22
22
import java .util .HashMap ;
23
23
import java .util .List ;
24
24
import java .util .Map ;
25
+ import java .util .Optional ;
25
26
import java .util .Set ;
26
27
import java .util .stream .Collectors ;
27
28
import javax .annotation .Nullable ;
35
36
import org .sonar .plugins .python .api .tree .Argument ;
36
37
import org .sonar .plugins .python .api .tree .CallExpression ;
37
38
import org .sonar .plugins .python .api .tree .Expression ;
39
+ import org .sonar .plugins .python .api .tree .FunctionDef ;
38
40
import org .sonar .plugins .python .api .tree .Name ;
39
41
import org .sonar .plugins .python .api .tree .QualifiedExpression ;
40
42
import org .sonar .plugins .python .api .tree .RegularArgument ;
41
43
import org .sonar .plugins .python .api .tree .Tree ;
42
44
import org .sonar .python .semantic .FunctionSymbolImpl ;
43
45
import org .sonar .python .tree .TreeUtils ;
44
46
47
+ import static org .sonar .plugins .python .api .symbols .Usage .Kind .PARAMETER ;
48
+
45
49
@ Rule (key = "S930" )
46
50
public class ArgumentNumberCheck extends PythonSubscriptionCheck {
47
51
@@ -66,7 +70,7 @@ public void initialize(Context context) {
66
70
67
71
private static void checkPositionalParameters (SubscriptionContext ctx , CallExpression callExpression , FunctionSymbol functionSymbol ) {
68
72
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 ()) ) {
70
74
self = 1 ;
71
75
}
72
76
Map <String , FunctionSymbol .Parameter > positionalParamsWithoutDefault = positionalParamsWithoutDefault (functionSymbol );
@@ -101,6 +105,25 @@ private static void checkPositionalParameters(SubscriptionContext ctx, CallExpre
101
105
}
102
106
}
103
107
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
+
104
127
private static Map <String , FunctionSymbol .Parameter > positionalParamsWithoutDefault (FunctionSymbol functionSymbol ) {
105
128
int unnamedIndex = 0 ;
106
129
Map <String , FunctionSymbol .Parameter > result = new HashMap <>();
0 commit comments