24
24
import java .util .Objects ;
25
25
import java .util .Optional ;
26
26
import java .util .Set ;
27
+ import java .util .function .Predicate ;
27
28
import java .util .regex .Pattern ;
28
29
import org .sonar .check .Rule ;
29
30
import org .sonar .check .RuleProperty ;
36
37
import org .sonar .plugins .python .api .tree .AnnotatedAssignment ;
37
38
import org .sonar .plugins .python .api .tree .AssignmentExpression ;
38
39
import org .sonar .plugins .python .api .tree .AssignmentStatement ;
40
+ import org .sonar .plugins .python .api .tree .CompoundAssignmentStatement ;
39
41
import org .sonar .plugins .python .api .tree .ComprehensionExpression ;
40
42
import org .sonar .plugins .python .api .tree .DictCompExpression ;
41
43
import org .sonar .plugins .python .api .tree .ExceptClause ;
44
+ import org .sonar .plugins .python .api .tree .Expression ;
42
45
import org .sonar .plugins .python .api .tree .ExpressionList ;
43
46
import org .sonar .plugins .python .api .tree .ForStatement ;
44
47
import org .sonar .plugins .python .api .tree .FunctionDef ;
@@ -77,11 +80,16 @@ public class UnusedLocalVariableCheck extends PythonSubscriptionCheck {
77
80
public void initialize (Context context ) {
78
81
pattern = Pattern .compile (format );
79
82
context .registerSyntaxNodeConsumer (Tree .Kind .FILE_INPUT , this ::checkTemplateVariablesAccessEnabled );
80
- context .registerSyntaxNodeConsumer (Kind .FUNCDEF , ctx -> checkLocalVars (ctx , ctx .syntaxNode (), ((FunctionDef ) ctx .syntaxNode ()).localVariables ()));
81
- context .registerSyntaxNodeConsumer (Kind .DICT_COMPREHENSION , ctx -> checkLocalVars (ctx , ctx .syntaxNode (), ((DictCompExpression ) ctx .syntaxNode ()).localVariables ()));
82
- context .registerSyntaxNodeConsumer (Kind .LIST_COMPREHENSION , ctx -> checkLocalVars (ctx , ctx .syntaxNode (), ((ComprehensionExpression ) ctx .syntaxNode ()).localVariables ()));
83
- context .registerSyntaxNodeConsumer (Kind .SET_COMPREHENSION , ctx -> checkLocalVars (ctx , ctx .syntaxNode (), ((ComprehensionExpression ) ctx .syntaxNode ()).localVariables ()));
84
- context .registerSyntaxNodeConsumer (Kind .GENERATOR_EXPR , ctx -> checkLocalVars (ctx , ctx .syntaxNode (), ((ComprehensionExpression ) ctx .syntaxNode ()).localVariables ()));
83
+ context .registerSyntaxNodeConsumer (Kind .FUNCDEF , ctx -> checkLocalVars (ctx , ctx .syntaxNode (),
84
+ ((FunctionDef ) ctx .syntaxNode ()).localVariables ()));
85
+ context .registerSyntaxNodeConsumer (Kind .DICT_COMPREHENSION , ctx -> checkLocalVars (ctx , ctx .syntaxNode (),
86
+ ((DictCompExpression ) ctx .syntaxNode ()).localVariables ()));
87
+ context .registerSyntaxNodeConsumer (Kind .LIST_COMPREHENSION , ctx -> checkLocalVars (ctx , ctx .syntaxNode (),
88
+ ((ComprehensionExpression ) ctx .syntaxNode ()).localVariables ()));
89
+ context .registerSyntaxNodeConsumer (Kind .SET_COMPREHENSION , ctx -> checkLocalVars (ctx , ctx .syntaxNode (),
90
+ ((ComprehensionExpression ) ctx .syntaxNode ()).localVariables ()));
91
+ context .registerSyntaxNodeConsumer (Kind .GENERATOR_EXPR , ctx -> checkLocalVars (ctx , ctx .syntaxNode (),
92
+ ((ComprehensionExpression ) ctx .syntaxNode ()).localVariables ()));
85
93
}
86
94
87
95
private void checkTemplateVariablesAccessEnabled (SubscriptionContext ctx ) {
@@ -103,6 +111,7 @@ private void checkLocalVars(SubscriptionContext ctx, Tree functionTree, Set<Symb
103
111
symbols .stream ()
104
112
.filter (s -> !pattern .matcher (s .name ()).matches ())
105
113
.filter (UnusedLocalVariableCheck ::hasOnlyBindingUsages )
114
+ .filter (UnusedLocalVariableCheck ::isNotUpdatingParameterDict )
106
115
.filter (symbol -> !isVariableAccessedInStringTemplate (symbol , stringLiteralValuesCollector ))
107
116
.forEach (symbol -> {
108
117
var usages = symbol .usages ().stream ()
@@ -149,7 +158,8 @@ public PreciseIssue createIssue(SubscriptionContext ctx, Symbol symbol, Usage us
149
158
150
159
private static void createAssignmentQuickFix (Usage usage , PreciseIssue issue ) {
151
160
if (usage .kind ().equals (Usage .Kind .ASSIGNMENT_LHS )) {
152
- Statement assignmentStatement = ((Statement ) TreeUtils .firstAncestorOfKind (usage .tree (), Kind .ASSIGNMENT_STMT , Kind .ANNOTATED_ASSIGNMENT ));
161
+ Statement assignmentStatement = ((Statement ) TreeUtils .firstAncestorOfKind (usage .tree (), Kind .ASSIGNMENT_STMT ,
162
+ Kind .ANNOTATED_ASSIGNMENT ));
153
163
154
164
Optional .ofNullable (assignmentStatement ).filter (stmt -> stmt .is (Kind .ASSIGNMENT_STMT )).map (AssignmentStatement .class ::cast ).ifPresent (stmt -> {
155
165
PythonQuickFix quickFix = PythonQuickFix .newQuickFix (ASSIGNMENT_QUICK_FIX_MESSAGE ,
@@ -167,7 +177,7 @@ private static void createAssignmentQuickFix(Usage usage, PreciseIssue issue) {
167
177
Tree assignmentTree = TreeUtils .firstAncestorOfKind (usage .tree (), Kind .ASSIGNMENT_EXPRESSION );
168
178
Optional .ofNullable (assignmentTree ).map (AssignmentExpression .class ::cast ).ifPresent (assignmentExpr -> {
169
179
PythonQuickFix quickFix = PythonQuickFix .newQuickFix (ASSIGNMENT_QUICK_FIX_MESSAGE ,
170
- createAssignmentExpressionQuickFix (usage , assignmentExpr ));
180
+ createAssignmentExpressionQuickFix (usage , assignmentExpr ));
171
181
issue .addQuickFix (quickFix );
172
182
});
173
183
}
@@ -188,9 +198,11 @@ private static boolean isUnderscoreSymbolAlreadyAssigned(SubscriptionContext ctx
188
198
Tree searchTree = usage .kind ().equals (Usage .Kind .LOOP_DECLARATION ) ? ctx .syntaxNode () : null ;
189
199
while (foundUnderscoreSymbol == null && searchTree != null ) {
190
200
if (searchTree .is (Kind .FUNCDEF )) {
191
- foundUnderscoreSymbol = ((FunctionDef ) searchTree ).localVariables ().stream ().filter (symbol1 -> "_" .equals (symbol1 .name ())).findAny ().orElse (null );
201
+ foundUnderscoreSymbol =
202
+ ((FunctionDef ) searchTree ).localVariables ().stream ().filter (symbol1 -> "_" .equals (symbol1 .name ())).findAny ().orElse (null );
192
203
} else if (searchTree .is (Kind .FILE_INPUT )) {
193
- foundUnderscoreSymbol = ((FileInputImpl ) searchTree ).globalVariables ().stream ().filter (symbol1 -> "_" .equals (symbol1 .name ())).findAny ().orElse (null );
204
+ foundUnderscoreSymbol =
205
+ ((FileInputImpl ) searchTree ).globalVariables ().stream ().filter (symbol1 -> "_" .equals (symbol1 .name ())).findAny ().orElse (null );
194
206
}
195
207
searchTree = TreeUtils .firstAncestor (searchTree , a -> a .is (Kind .FUNCDEF , Kind .FILE_INPUT ));
196
208
}
@@ -199,7 +211,8 @@ private static boolean isUnderscoreSymbolAlreadyAssigned(SubscriptionContext ctx
199
211
200
212
private static boolean isLoopIndex (Usage usage , Symbol symbol ) {
201
213
var allowedKinds = EnumSet .of (Usage .Kind .LOOP_DECLARATION , Usage .Kind .COMP_DECLARATION );
202
- Optional <Symbol > optionalSymbol = Optional .of (usage ).filter (u -> allowedKinds .contains (u .kind ())).map (Usage ::tree ).map (a -> ((Name ) a ).symbol ());
214
+ Optional <Symbol > optionalSymbol =
215
+ Optional .of (usage ).filter (u -> allowedKinds .contains (u .kind ())).map (Usage ::tree ).map (a -> ((Name ) a ).symbol ());
203
216
return optionalSymbol .map (value -> value .equals (symbol )).orElse (false );
204
217
}
205
218
@@ -227,19 +240,39 @@ private static boolean hasOnlyBindingUsages(Symbol symbol) {
227
240
return false ;
228
241
}
229
242
return usages .stream ().noneMatch (usage -> usage .kind () == Usage .Kind .IMPORT )
230
- && usages .stream ().allMatch (Usage ::isBindingUsage );
243
+ && usages .stream ().allMatch (Usage ::isBindingUsage );
244
+ }
245
+
246
+ private static boolean isNotUpdatingParameterDict (Symbol symbol ) {
247
+ List <Usage > usages = symbol .usages ();
248
+ return usages .stream ().noneMatch (UnusedLocalVariableCheck ::isDictAssignmentExpressionUsage ) ||
249
+ usages .stream ().noneMatch (usage -> usage .kind () == Usage .Kind .PARAMETER );
250
+ }
251
+
252
+ private static boolean isDictAssignmentExpressionUsage (Usage usage ) {
253
+ Tree compoundAssignmentTree = TreeUtils .firstAncestorOfKind (usage .tree (), Kind .COMPOUND_ASSIGNMENT );
254
+ return compoundAssignmentTree instanceof CompoundAssignmentStatement compoundAssignmentStatement &&
255
+ "|=" .equals (compoundAssignmentStatement .compoundAssignmentToken ().value ()) &&
256
+ compoundAssignmentStatement .lhsExpression ().type ().mustBeOrExtend ("dict" );
231
257
}
232
258
233
259
private static boolean isOnlyTypeAnnotation (List <Usage > usages ) {
234
260
return usages .size () == 1 && usages .get (0 ).isBindingUsage () &&
235
- TreeUtils .firstAncestor (usages .get (0 ).tree (), t -> t .is (Kind .ANNOTATED_ASSIGNMENT ) && ((AnnotatedAssignment ) t ).assignedValue () == null ) != null ;
261
+ TreeUtils .firstAncestor (usages .get (0 ).tree (),
262
+ t -> t .is (Kind .ANNOTATED_ASSIGNMENT ) && ((AnnotatedAssignment ) t ).assignedValue () == null ) != null ;
236
263
}
237
264
238
265
private static boolean isTupleDeclaration (Usage usage ) {
239
266
var tree = usage .tree ();
240
- return !isSequenceUnpacking (usage ) && TreeUtils .firstAncestor (tree , t -> t .is (Kind .TUPLE )
241
- || (t .is (Kind .EXPRESSION_LIST ) && ((ExpressionList ) t ).expressions ().size () > 1 )
242
- || (t .is (Kind .FOR_STMT ) && ((ForStatement ) t ).expressions ().size () > 1 && ((ForStatement ) t ).expressions ().contains (tree ))) != null ;
267
+
268
+ Predicate <Tree > isTupleDeclaration = t -> t .is (Kind .TUPLE )
269
+ || (t .is (Kind .EXPRESSION_LIST ) && ((ExpressionList ) t ).expressions ().size () > 1 )
270
+ || (t .is (Kind .FOR_STMT )
271
+ && ((ForStatement ) t ).expressions ().size () > 1
272
+ && tree instanceof Expression treeExpr
273
+ && ((ForStatement ) t ).expressions ().contains (treeExpr ));
274
+
275
+ return !isSequenceUnpacking (usage ) && TreeUtils .firstAncestor (tree , isTupleDeclaration ) != null ;
243
276
}
244
277
245
278
private static boolean isSequenceUnpacking (Usage usage ) {
0 commit comments