1616 */
1717package org .sonar .python .checks ;
1818
19- import java .util .Collection ;
2019import java .util .HashMap ;
2120import java .util .List ;
2221import java .util .Map ;
23- import java .util .Optional ;
2422import java .util .Set ;
2523import java .util .stream .Collectors ;
26- import java .util .stream .Stream ;
27- import javax .annotation .Nullable ;
2824import org .sonar .check .Rule ;
29- import org .sonar .plugins .python .api .LocationInFile ;
3025import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
3126import org .sonar .plugins .python .api .SubscriptionContext ;
32- import org .sonar .plugins .python .api .symbols .ClassSymbol ;
33- import org .sonar .plugins .python .api .symbols .FunctionSymbol ;
34- import org .sonar .plugins .python .api .symbols .Symbol ;
3527import org .sonar .plugins .python .api .tree .Argument ;
3628import org .sonar .plugins .python .api .tree .CallExpression ;
3729import org .sonar .plugins .python .api .tree .Expression ;
38- import org .sonar .plugins .python .api .tree .FunctionDef ;
3930import org .sonar .plugins .python .api .tree .Name ;
4031import org .sonar .plugins .python .api .tree .QualifiedExpression ;
4132import org .sonar .plugins .python .api .tree .RegularArgument ;
4233import org .sonar .plugins .python .api .tree .Tree ;
43- import org .sonar .python .semantic .FunctionSymbolImpl ;
44- import org .sonar .python .semantic .SymbolUtils ;
45- import org .sonar .python .tree .TreeUtils ;
46-
47- import static org .sonar .plugins .python .api .symbols .Usage .Kind .PARAMETER ;
34+ import org .sonar .plugins .python .api .types .v2 .FunctionType ;
35+ import org .sonar .plugins .python .api .types .v2 .ObjectType ;
36+ import org .sonar .plugins .python .api .types .v2 .ParameterV2 ;
37+ import org .sonar .python .api .types .v2 .matchers .TypeMatchers ;
4838
4939@ Rule (key = "S930" )
5040public class ArgumentNumberCheck extends PythonSubscriptionCheck {
@@ -56,31 +46,27 @@ public void initialize(Context context) {
5646 context .registerSyntaxNodeConsumer (Tree .Kind .CALL_EXPR , ctx -> {
5747 CallExpression callExpression = (CallExpression ) ctx .syntaxNode ();
5848
59- Optional .of (callExpression )
60- .map (CallExpression ::calleeSymbol )
61- .map (SymbolUtils ::getFunctionSymbols )
62- .filter (SymbolUtils ::isEqualParameterCountAndNames )
63- .map (Collection ::stream )
64- .flatMap (Stream ::findFirst )
65- .ifPresent (functionSymbol -> checkFunctionSymbol (ctx , callExpression , functionSymbol ));
49+ if (callExpression .callee ().typeV2 () instanceof FunctionType functionType ) {
50+ checkFunctionType (ctx , callExpression , functionType );
51+ }
6652 });
6753 }
6854
69- private static void checkFunctionSymbol (SubscriptionContext ctx , CallExpression callExpression , FunctionSymbol functionSymbol ) {
70- if (isException (callExpression , functionSymbol )) {
55+ private static void checkFunctionType (SubscriptionContext ctx , CallExpression callExpression , FunctionType functionType ) {
56+ if (isException (ctx , callExpression , functionType )) {
7157 return ;
7258 }
73- checkPositionalParameters (ctx , callExpression , functionSymbol );
74- checkKeywordArguments (ctx , callExpression , functionSymbol , callExpression .callee ());
59+ checkPositionalParameters (ctx , callExpression , functionType );
60+ checkKeywordArguments (ctx , callExpression , functionType , callExpression .callee ());
7561 }
7662
77- private static void checkPositionalParameters (SubscriptionContext ctx , CallExpression callExpression , FunctionSymbol functionSymbol ) {
63+ private static void checkPositionalParameters (SubscriptionContext ctx , CallExpression callExpression , FunctionType functionType ) {
7864 int self = 0 ;
79- if (functionSymbol . isInstanceMethod () && callExpression . callee (). is ( Tree . Kind . QUALIFIED_EXPR ) && ! isCalledAsClassMethod (( QualifiedExpression ) callExpression . callee () )) {
65+ if (isCalledAsInstanceMethod ( callExpression , functionType ) && functionHasSelfParameter ( functionType )) {
8066 self = 1 ;
8167 }
82- Map <String , FunctionSymbol . Parameter > positionalParamsWithoutDefault = positionalParamsWithoutDefault (functionSymbol );
83- long nbPositionalParamsWithDefault = functionSymbol .parameters ().stream ()
68+ Map <String , ParameterV2 > positionalParamsWithoutDefault = positionalParamsWithoutDefault (functionType );
69+ long nbPositionalParamsWithDefault = functionType .parameters ().stream ()
8470 .filter (parameterName -> !parameterName .isKeywordOnly () && parameterName .hasDefaultValue ())
8571 .count ();
8672
@@ -101,42 +87,33 @@ private static void checkPositionalParameters(SubscriptionContext ctx, CallExpre
10187 if (nbPositionalParamsWithDefault > 0 ) {
10288 expected = "at least " + expected ;
10389 }
104- addPositionalIssue (ctx , callExpression .callee (), functionSymbol , message , expected );
90+ addPositionalIssue (ctx , callExpression .callee (), functionType , message , expected );
10591 } else if (nbMissingArgs + nbPositionalParamsWithDefault + nbNonKeywordOnlyPassedWithKeyword < 0 ) {
10692 String message = "Remove " + (- nbMissingArgs - nbPositionalParamsWithDefault ) + " unexpected arguments; " ;
10793 if (nbPositionalParamsWithDefault > 0 ) {
10894 expected = "at most " + (minimumPositionalArgs - self + nbPositionalParamsWithDefault );
10995 }
110- addPositionalIssue (ctx , callExpression .callee (), functionSymbol , message , expected );
96+ addPositionalIssue (ctx , callExpression .callee (), functionType , message , expected );
11197 }
11298 }
11399
114- private static boolean isCalledAsClassMethod ( QualifiedExpression callee ) {
115- return TreeUtils . getSymbolFromTree ( callee . qualifier () )
116- . filter ( ArgumentNumberCheck :: isParamOfClassMethod )
117- . isPresent () ;
100+ private static boolean isCalledAsInstanceMethod ( CallExpression callExpression , FunctionType functionType ) {
101+ return functionType . isInstanceMethod ( )
102+ && callExpression . callee () instanceof QualifiedExpression qualifiedExpression
103+ && qualifiedExpression . qualifier (). typeV2 () instanceof ObjectType ;
118104 }
119105
120- // no need to check that's the first parameter (i.e. cls)
121- // the assumption is that another method can be called only using the first parameter of a class method
122- private static boolean isParamOfClassMethod (Symbol symbol ) {
123- return symbol .usages ().stream ().anyMatch (usage -> usage .kind () == PARAMETER && isParamOfClassMethod (usage .tree ()));
106+ private static boolean functionHasSelfParameter (FunctionType functionType ) {
107+ return !functionType .parameters ().isEmpty ();
124108 }
125109
126- private static boolean isParamOfClassMethod (Tree tree ) {
127- FunctionDef functionDef = (FunctionDef ) TreeUtils .firstAncestorOfKind (tree , Tree .Kind .FUNCDEF );
128- return Optional .ofNullable (TreeUtils .getFunctionSymbolFromDef (functionDef ))
129- .filter (functionSymbol -> functionSymbol .decorators ().stream ().anyMatch ("classmethod" ::equals ))
130- .isPresent ();
131- }
132-
133- private static Map <String , FunctionSymbol .Parameter > positionalParamsWithoutDefault (FunctionSymbol functionSymbol ) {
110+ private static Map <String , ParameterV2 > positionalParamsWithoutDefault (FunctionType functionType ) {
134111 int unnamedIndex = 0 ;
135- Map <String , FunctionSymbol . Parameter > result = new HashMap <>();
136- for (FunctionSymbol . Parameter parameter : functionSymbol .parameters ()) {
112+ Map <String , ParameterV2 > result = new HashMap <>();
113+ for (ParameterV2 parameter : functionType .parameters ()) {
137114 if (!parameter .isKeywordOnly () && !parameter .hasDefaultValue ()) {
138115 String name = parameter .name ();
139- if (name == null ) {
116+ if (name == null || name . isEmpty () ) {
140117 result .put ("!unnamed" + unnamedIndex , parameter );
141118 unnamedIndex ++;
142119 } else {
@@ -147,54 +124,59 @@ private static Map<String, FunctionSymbol.Parameter> positionalParamsWithoutDefa
147124 return result ;
148125 }
149126
150- private static void addPositionalIssue (SubscriptionContext ctx , Tree tree , FunctionSymbol functionSymbol , String message , String expected ) {
151- String msg = message + "'" + functionSymbol .name () + "' expects " + expected + " positional arguments." ;
127+ private static void addPositionalIssue (SubscriptionContext ctx , Tree tree , FunctionType functionType , String message , String expected ) {
128+ String msg = message + "'" + functionType .name () + "' expects " + expected + " positional arguments." ;
152129 PreciseIssue preciseIssue = ctx .addIssue (tree , msg );
153- addSecondary (functionSymbol , preciseIssue );
130+ addSecondary (functionType , preciseIssue );
154131 }
155132
156- private static boolean isReceiverClassSymbol (QualifiedExpression qualifiedExpression ) {
157- return TreeUtils .getSymbolFromTree (qualifiedExpression .qualifier ())
158- .filter (symbol -> symbol .kind () == Symbol .Kind .CLASS )
159- .isPresent ();
133+ private static boolean isException (SubscriptionContext ctx , CallExpression callExpression , FunctionType functionType ) {
134+ return functionType .hasDecorators ()
135+ || functionType .hasVariadicParameter ()
136+ || callExpression .arguments ().stream ().anyMatch (argument -> argument .is (Tree .Kind .UNPACKING_EXPR ))
137+ || extendsZopeInterface (ctx , callExpression )
138+ || isCalledAsBoundInstanceMethod (callExpression )
139+ || isSuperCall (ctx , callExpression );
160140 }
161141
162- private static boolean isException (CallExpression callExpression , FunctionSymbol functionSymbol ) {
163- return functionSymbol .hasDecorators ()
164- || functionSymbol .hasVariadicParameter ()
165- || callExpression .arguments ().stream ().anyMatch (argument -> argument .is (Tree .Kind .UNPACKING_EXPR ))
166- || extendsZopeInterface (((FunctionSymbolImpl ) functionSymbol ).owner ())
167- // TODO: distinguish between class methods (new and old style) from other methods
168- || (callExpression .callee ().is (Tree .Kind .QUALIFIED_EXPR ) && isReceiverClassSymbol (((QualifiedExpression ) callExpression .callee ())));
142+ private static boolean extendsZopeInterface (SubscriptionContext ctx , CallExpression callExpression ) {
143+ var matcher = TypeMatchers .isFunctionOwnerSatisfying (
144+ TypeMatchers .isOrExtendsType ("zope.interface.Interface" )
145+ );
146+ return matcher .isTrueFor (callExpression .callee (), ctx );
169147 }
170148
171- private static boolean extendsZopeInterface ( @ Nullable Symbol symbol ) {
172- if (symbol != null && symbol . kind () == Symbol . Kind . CLASS ) {
173- return (( ClassSymbol ) symbol ). isOrExtends ( "zope.interface.Interface" );
149+ private static boolean isCalledAsBoundInstanceMethod ( CallExpression callExpression ) {
150+ if (callExpression . callee (). typeV2 () instanceof FunctionType functionType ) {
151+ return functionType . isInstanceMethod () && ! callExpression . callee (). is ( Tree . Kind . QUALIFIED_EXPR );
174152 }
175153 return false ;
176154 }
177155
178- private static void addSecondary (FunctionSymbol functionSymbol , PreciseIssue preciseIssue ) {
179- LocationInFile definitionLocation = functionSymbol .definitionLocation ();
180- if (definitionLocation != null ) {
181- preciseIssue .secondary (definitionLocation , FUNCTION_DEFINITION );
156+ private static boolean isSuperCall (SubscriptionContext ctx , CallExpression callExpression ) {
157+ if (callExpression .callee () instanceof QualifiedExpression qualifiedExpression ) {
158+ return TypeMatchers .isObjectOfType ("super" ).isTrueFor (qualifiedExpression .qualifier (), ctx );
182159 }
160+ return false ;
161+ }
162+
163+ private static void addSecondary (FunctionType functionType , PreciseIssue preciseIssue ) {
164+ functionType .definitionLocation ().ifPresent (location -> preciseIssue .secondary (location , FUNCTION_DEFINITION ));
183165 }
184166
185- private static void checkKeywordArguments (SubscriptionContext ctx , CallExpression callExpression , FunctionSymbol functionSymbol , Expression callee ) {
186- List <FunctionSymbol . Parameter > parameters = functionSymbol .parameters ();
167+ private static void checkKeywordArguments (SubscriptionContext ctx , CallExpression callExpression , FunctionType functionType , Expression callee ) {
168+ List <ParameterV2 > parameters = functionType .parameters ();
187169 Set <String > mandatoryParamNamesKeywordOnly = parameters .stream ()
188170 .filter (parameterName -> parameterName .isKeywordOnly () && !parameterName .hasDefaultValue ())
189- .map (FunctionSymbol . Parameter ::name ).collect (Collectors .toSet ());
171+ .map (ParameterV2 ::name ).collect (Collectors .toSet ());
190172
191173 for (Argument argument : callExpression .arguments ()) {
192174 RegularArgument arg = (RegularArgument ) argument ;
193175 Name keyword = arg .keywordArgument ();
194176 if (keyword != null ) {
195177 if (parameters .stream ().noneMatch (parameter -> keyword .name ().equals (parameter .name ()) && !parameter .isPositionalOnly ())) {
196178 PreciseIssue preciseIssue = ctx .addIssue (argument , "Remove this unexpected named argument '" + keyword .name () + "'." );
197- addSecondary (functionSymbol , preciseIssue );
179+ addSecondary (functionType , preciseIssue );
198180 } else {
199181 mandatoryParamNamesKeywordOnly .remove (keyword .name ());
200182 }
@@ -206,7 +188,7 @@ private static void checkKeywordArguments(SubscriptionContext ctx, CallExpressio
206188 message .append ("'" ).append (param ).append ("' " );
207189 }
208190 PreciseIssue preciseIssue = ctx .addIssue (callee , message .toString ().trim ());
209- addSecondary (functionSymbol , preciseIssue );
191+ addSecondary (functionType , preciseIssue );
210192 }
211193 }
212194}
0 commit comments