36
36
import org .sonar .plugins .python .api .symbols .Symbol ;
37
37
import org .sonar .plugins .python .api .symbols .Usage ;
38
38
import org .sonar .plugins .python .api .tree .AssignmentStatement ;
39
+ import org .sonar .plugins .python .api .tree .CallExpression ;
39
40
import org .sonar .plugins .python .api .tree .CompoundAssignmentStatement ;
40
41
import org .sonar .plugins .python .api .tree .Expression ;
41
42
import org .sonar .plugins .python .api .tree .FunctionDef ;
45
46
import org .sonar .plugins .python .api .tree .SubscriptionExpression ;
46
47
import org .sonar .plugins .python .api .tree .Tree ;
47
48
import org .sonar .plugins .python .api .types .BuiltinTypes ;
49
+ import org .sonar .python .quickfix .IssueWithQuickFix ;
50
+ import org .sonar .python .quickfix .PythonQuickFix ;
48
51
import org .sonar .python .tree .TreeUtils ;
49
52
50
53
import static org .sonar .plugins .python .api .tree .Tree .Kind .ASSIGNMENT_STMT ;
54
+ import static org .sonar .plugins .python .api .tree .Tree .Kind .CALL_EXPR ;
51
55
import static org .sonar .plugins .python .api .tree .Tree .Kind .COMPOUND_ASSIGNMENT ;
52
56
import static org .sonar .plugins .python .api .tree .Tree .Kind .DEL_STMT ;
53
57
import static org .sonar .plugins .python .api .tree .Tree .Kind .FUNCDEF ;
54
58
import static org .sonar .plugins .python .api .tree .Tree .Kind .NAME ;
55
59
import static org .sonar .plugins .python .api .tree .Tree .Kind .QUALIFIED_EXPR ;
56
60
import static org .sonar .plugins .python .api .tree .Tree .Kind .SUBSCRIPTION ;
61
+ import static org .sonar .python .quickfix .PythonTextEdit .insertLineBefore ;
62
+ import static org .sonar .python .quickfix .PythonTextEdit .replace ;
57
63
import static org .sonar .python .tree .TreeUtils .getSymbolFromTree ;
58
64
import static org .sonar .python .tree .TreeUtils .nonTupleParameters ;
59
65
60
66
@ Rule (key = "S5717" )
61
67
public class ModifiedParameterValueCheck extends PythonSubscriptionCheck {
62
68
69
+ private static final String MESSAGE = "Change this default value to \" None\" and initialize this parameter inside the function/method." ;
63
70
private static final String MODIFIED_SECONDARY = "The parameter is modified." ;
64
71
private static final String ASSIGNED_SECONDARY = "The parameter is stored in another object." ;
65
72
@@ -113,16 +120,55 @@ public void initialize(Context context) {
113
120
if (TreeUtils .firstAncestorOfKind (functionDef , FUNCDEF ) != null ) {
114
121
return ;
115
122
}
123
+
116
124
for (Parameter parameter : nonTupleParameters (functionDef )) {
117
- Map <Tree , String > mutations = getMutations (parameter );
118
- if (!mutations .isEmpty ()) {
119
- PreciseIssue preciseIssue = ctx .addIssue (parameter , "Change this default value to \" None\" and initialize this parameter inside the function/method." );
120
- mutations .keySet ().forEach (t -> preciseIssue .secondary (t , mutations .get (t )));
125
+ Expression defaultValue = parameter .defaultValue ();
126
+ if (defaultValue == null ) {
127
+ continue ;
121
128
}
129
+
130
+ getSymbolFromTree (parameter .name ())
131
+ .filter (symbol -> !isUsingMemoization (symbol ))
132
+ .ifPresent (paramSymbol -> {
133
+ Map <Tree , String > mutations = getMutations (defaultValue , paramSymbol );
134
+ if (!mutations .isEmpty ()) {
135
+ IssueWithQuickFix issue = (IssueWithQuickFix ) ctx .addIssue (parameter , MESSAGE );
136
+ mutations .keySet ().forEach (t -> issue .secondary (t , mutations .get (t )));
137
+
138
+ getQuickFix (functionDef , defaultValue , paramSymbol )
139
+ .ifPresent (issue ::addQuickFix );
140
+ }
141
+ }
142
+ );
122
143
}
123
144
});
124
145
}
125
146
147
+ // We use "\n" systematically, the IDE will decide which one to use,
148
+ // therefore suppressing java:S3457 (Printf-style format strings should be used correctly)
149
+ @ SuppressWarnings ("java:S3457" )
150
+ private static Optional <PythonQuickFix > getQuickFix (FunctionDef functionDef , Expression defaultValue , Symbol paramSymbol ) {
151
+ Tree firstStatement = functionDef .body ().statements ().get (0 );
152
+ String paramName = paramSymbol .name ();
153
+
154
+ return parameterInitialization (defaultValue ).map (
155
+ paramInit -> PythonQuickFix .newQuickFix ("Initialize this parameter inside the function/method" )
156
+ .addTextEdit (replace (defaultValue , "None" ))
157
+ .addTextEdit (insertLineBefore (firstStatement , String .format ("if %1$s is None:\n %1$s = %2$s()\n " , paramName , paramInit )))
158
+ .build ()
159
+ );
160
+ }
161
+
162
+ private static Optional <String > parameterInitialization (Expression defaultValue ) {
163
+ if (defaultValue .is (CALL_EXPR )) {
164
+ CallExpression call = (CallExpression ) defaultValue ;
165
+ return Optional .ofNullable (call .calleeSymbol ())
166
+ .map (symbol -> call .callee ().is (QUALIFIED_EXPR ) ? symbol .fullyQualifiedName () : symbol .name ());
167
+ } else {
168
+ return Optional .ofNullable (defaultValueType (defaultValue ));
169
+ }
170
+ }
171
+
126
172
@ CheckForNull
127
173
private static String defaultValueType (Expression expression ) {
128
174
for (String nonCompliantType : MUTATING_METHODS .keySet ()) {
@@ -137,19 +183,9 @@ private static boolean isUsingMemoization(Symbol symbol) {
137
183
return symbol .name ().contains ("cache" ) || symbol .name ().contains ("memo" );
138
184
}
139
185
140
- private static Map <Tree , String > getMutations (Parameter parameter ) {
141
- Expression defaultValue = parameter .defaultValue ();
142
- if (defaultValue == null ) {
143
- return Collections .emptyMap ();
144
- }
145
-
146
- Optional <Symbol > paramSymbol = getSymbolFromTree (parameter .name ());
147
- if (!paramSymbol .isPresent () || isUsingMemoization (paramSymbol .get ())) {
148
- return Collections .emptyMap ();
149
- }
150
-
186
+ private static Map <Tree , String > getMutations (Expression defaultValue , Symbol paramSymbol ) {
151
187
if (!defaultValue .type ().canOnlyBe (BuiltinTypes .NONE_TYPE )) {
152
- List <Tree > attributeSet = getAttributeSet (paramSymbol . get () );
188
+ List <Tree > attributeSet = getAttributeSet (paramSymbol );
153
189
if (!attributeSet .isEmpty ()) {
154
190
return attributeSet .stream ().collect (Collectors .toMap (tree -> tree , tree -> MODIFIED_SECONDARY ));
155
191
}
@@ -160,18 +196,19 @@ private static Map<Tree, String> getMutations(Parameter parameter) {
160
196
return Collections .emptyMap ();
161
197
}
162
198
Map <Tree , String > mutations = new HashMap <>();
163
- for (Usage usage : paramSymbol .get (). usages ()) {
199
+ for (Usage usage : paramSymbol .usages ()) {
164
200
getKindOfWriteUsage (paramSymbol , defaultValueType , typeMutatingMethods , usage ).ifPresent (s -> mutations .put (usage .tree ().parent (), s ));
165
201
}
166
202
return mutations ;
167
203
}
168
204
169
- private static Optional <String > getKindOfWriteUsage (Optional < Symbol > paramSymbol , @ Nullable String defaultValueType , Set <String > typeMutatingMethods , Usage usage ) {
205
+ private static Optional <String > getKindOfWriteUsage (Symbol paramSymbol , @ Nullable String defaultValueType , Set <String > typeMutatingMethods , Usage usage ) {
170
206
Tree parent = usage .tree ().parent ();
171
207
if (parent .is (QUALIFIED_EXPR )) {
172
208
QualifiedExpression qualifiedExpression = (QualifiedExpression ) parent ;
173
- return getSymbolFromTree (qualifiedExpression .qualifier ()).equals (paramSymbol ) && isMutatingMethod (typeMutatingMethods , qualifiedExpression .name ().name ()) ?
174
- Optional .of (MODIFIED_SECONDARY ) : Optional .empty ();
209
+
210
+ return getSymbolFromTree (qualifiedExpression .qualifier ()).filter (paramSymbol ::equals ).isPresent ()
211
+ && isMutatingMethod (typeMutatingMethods , qualifiedExpression .name ().name ()) ? Optional .of (MODIFIED_SECONDARY ) : Optional .empty ();
175
212
}
176
213
if (isUsedInDelStatement (usage .tree ()) ||
177
214
isUsedInLhsOfAssignment (usage .tree (), exp -> isAccessingExpression (exp , usage .tree ())) ||
0 commit comments