24
24
import java .util .Map ;
25
25
import java .util .Optional ;
26
26
import java .util .Set ;
27
+ import java .util .stream .Collectors ;
27
28
import org .sonar .check .Rule ;
28
29
import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
29
30
import org .sonar .plugins .python .api .SubscriptionContext ;
31
+ import org .sonar .plugins .python .api .quickfix .PythonQuickFix ;
30
32
import org .sonar .plugins .python .api .symbols .Symbol ;
31
33
import org .sonar .plugins .python .api .tree .Argument ;
32
34
import org .sonar .plugins .python .api .tree .CallExpression ;
33
35
import org .sonar .plugins .python .api .tree .Tree ;
36
+ import org .sonar .python .quickfix .TextEditUtils ;
34
37
import org .sonar .python .tree .TreeUtils ;
35
38
36
39
@ Rule (key = "S6735" )
37
40
public class PandasAddMergeParametersCheck extends PythonSubscriptionCheck {
38
41
39
42
enum Keywords {
40
- HOW ("how" , 2 , 1 , 2 ),
41
- ON ("on" , 1 , 2 , 3 ),
42
- VALIDATE ("validate" , 6 , 11 , 12 );
43
+ HOW ("how" , 2 , 1 , 2 , " \" inner \" " , " \" left \" " ),
44
+ ON ("on" , 1 , 2 , 3 , "None" , "None" ),
45
+ VALIDATE ("validate" , 6 , 11 , 12 , " \" many_to_many \" " , " \" many_to_many \" " );
43
46
44
47
public String getKeyword () {
45
48
return keyword ;
@@ -62,24 +65,52 @@ public int getPositionInPandasMerge() {
62
65
final int positionInDataFrameMerge ;
63
66
final int positionInPandasMerge ;
64
67
65
- Keywords (String keyword , int positionInJoin , int positionInDataFrameMerge , int positionInPandasMerge ) {
68
+ final String defaultValueMerge ;
69
+ final String defaultValueJoin ;
70
+
71
+ String getReplacementText (String fullyQualifiedName ) {
72
+ if (DATAFRAME_JOIN_FQN .equals (fullyQualifiedName )) {
73
+ return String .format ("%s=%s" , this .keyword , this .defaultValueJoin );
74
+ } else {
75
+ return String .format ("%s=%s" , this .keyword , this .defaultValueMerge );
76
+ }
77
+ }
78
+
79
+ int getArgumentPosition (String fullyQualifiedName ) {
80
+ if (DATAFRAME_JOIN_FQN .equals (fullyQualifiedName )) {
81
+ return this .getPositionInJoin ();
82
+ } else if (DATAFRAME_MERGE_FQN .equals (fullyQualifiedName )) {
83
+ return this .getPositionInDataFrameMerge ();
84
+ }
85
+ return this .getPositionInPandasMerge ();
86
+ }
87
+
88
+ Keywords (String keyword , int positionInJoin , int positionInDataFrameMerge , int positionInPandasMerge , String defaultValueMerge , String defaultValueJoin ) {
66
89
this .keyword = keyword ;
67
90
this .positionInJoin = positionInJoin ;
68
91
this .positionInDataFrameMerge = positionInDataFrameMerge ;
69
92
this .positionInPandasMerge = positionInPandasMerge ;
93
+ this .defaultValueMerge = defaultValueMerge ;
94
+ this .defaultValueJoin = defaultValueJoin ;
70
95
}
71
96
}
72
97
98
+ private static final String DATAFRAME_JOIN_FQN = "pandas.core.frame.DataFrame.merge" ;
99
+ private static final String DATAFRAME_MERGE_FQN = "pandas.core.frame.DataFrame.join" ;
100
+ private static final String PANDAS_MERGE_FQN = "pandas.core.reshape.merge.merge" ;
101
+
73
102
private static final Set <String > METHODS = Set .of (
74
- "pandas.core.frame.DataFrame.merge" ,
75
- "pandas.core.reshape.merge.merge" ,
76
- "pandas.core.frame.DataFrame.join" );
103
+ DATAFRAME_JOIN_FQN ,
104
+ DATAFRAME_MERGE_FQN ,
105
+ PANDAS_MERGE_FQN );
77
106
78
107
private static final Map <Integer , String > MESSAGES = Map .of (
79
108
1 , "Specify the \" %s\" parameter of this %s." ,
80
109
2 , "Specify the \" %s\" and \" %s\" parameters of this %s." ,
81
110
3 , "Specify the \" %s\" , \" %s\" and \" %s\" parameters of this %s." );
82
111
112
+ private static final String QUICKFIX_MESSAGE = "Add the missing parameters" ;
113
+
83
114
@ Override
84
115
public void initialize (Context context ) {
85
116
context .registerSyntaxNodeConsumer (Tree .Kind .CALL_EXPR , PandasAddMergeParametersCheck ::verifyMergeCallParameters );
@@ -94,42 +125,44 @@ private static void verifyMergeCallParameters(SubscriptionContext ctx) {
94
125
}
95
126
96
127
private static void missingArguments (String fullyQualifiedName , SubscriptionContext ctx , CallExpression callExpression ) {
97
- List <String > parameters = new ArrayList <>();
128
+ List <Keywords > missingKeywords = new ArrayList <>();
98
129
if (isArgumentMissing (fullyQualifiedName , Keywords .HOW , callExpression .arguments ())) {
99
- parameters .add (Keywords .HOW . getKeyword () );
130
+ missingKeywords .add (Keywords .HOW );
100
131
}
101
132
if (isArgumentMissing (fullyQualifiedName , Keywords .ON , callExpression .arguments ())) {
102
- parameters .add (Keywords .ON . getKeyword () );
133
+ missingKeywords .add (Keywords .ON );
103
134
}
104
135
if (isArgumentMissing (fullyQualifiedName , Keywords .VALIDATE , callExpression .arguments ())) {
105
- parameters .add (Keywords .VALIDATE . getKeyword () );
136
+ missingKeywords .add (Keywords .VALIDATE );
106
137
}
107
- if (!parameters .isEmpty ()) {
108
- ctx .addIssue (callExpression , generateMessage (MESSAGES .get (numberOfMissingArguments (parameters )), parameters , fullyQualifiedName ));
138
+ if (!missingKeywords .isEmpty ()) {
139
+ PreciseIssue issue = ctx .addIssue (callExpression , generateMessage (MESSAGES .get (numberOfMissingArguments (missingKeywords )), missingKeywords , fullyQualifiedName ));
140
+ issue .addQuickFix (PythonQuickFix
141
+ .newQuickFix (QUICKFIX_MESSAGE )
142
+ .addTextEdit (
143
+ TextEditUtils .insertBefore (callExpression .rightPar (), getReplacementText (fullyQualifiedName , missingKeywords )))
144
+ .build ());
109
145
}
110
146
}
111
147
112
148
private static boolean isArgumentMissing (String fullyQualfiedName , Keywords keyword , List <Argument > arguments ) {
113
149
return Optional .of (keyword )
114
- .map (kw -> TreeUtils .nthArgumentOrKeyword (getArgumentPosition (fullyQualfiedName , kw ), kw .getKeyword (), arguments ))
150
+ .map (kw -> TreeUtils .nthArgumentOrKeyword (kw . getArgumentPosition (fullyQualfiedName ), kw .getKeyword (), arguments ))
115
151
.isEmpty ();
116
152
}
117
153
118
- private static int getArgumentPosition (String fullyQualifiedName , Keywords parameter ) {
119
- if ("pandas.core.frame.DataFrame.join" .equals (fullyQualifiedName )) {
120
- return parameter .getPositionInJoin ();
121
- } else if ("pandas.core.frame.DataFrame.merge" .equals (fullyQualifiedName )) {
122
- return parameter .getPositionInDataFrameMerge ();
123
- }
124
- return parameter .getPositionInPandasMerge ();
154
+ private static String generateMessage (String message , List <Keywords > missingKeywords , String functionName ) {
155
+ List <String > missingKeywordsList = missingKeywords .stream ().map (Keywords ::getKeyword ).collect (Collectors .toList ());
156
+ missingKeywordsList .add (functionName .substring (functionName .lastIndexOf ('.' ) + 1 ));
157
+ return String .format (message , missingKeywordsList .toArray ());
125
158
}
126
159
127
- private static String generateMessage (String message , List <String > missingKeywords , String functionName ) {
128
- missingKeywords .add (functionName .substring (functionName .lastIndexOf ('.' ) + 1 ));
129
- return String .format (message , missingKeywords .toArray ());
160
+ private static int numberOfMissingArguments (List <Keywords > missingKeywords ) {
161
+ return missingKeywords .size ();
130
162
}
131
163
132
- private static int numberOfMissingArguments (List <String > keywords ) {
133
- return keywords .size ();
164
+ private static String getReplacementText (String fullyQualifiedName , List <Keywords > missingKeywords ) {
165
+ return String .format (", %s" , missingKeywords .stream ().map (keyword -> keyword .getReplacementText (fullyQualifiedName ))
166
+ .collect (Collectors .joining (", " )));
134
167
}
135
168
}
0 commit comments