19
19
*/
20
20
package org .sonar .python .checks ;
21
21
22
+ import java .util .List ;
23
+ import java .util .Optional ;
22
24
import java .util .Set ;
23
25
import org .sonar .check .Rule ;
24
26
import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
25
27
import org .sonar .plugins .python .api .SubscriptionContext ;
26
28
import org .sonar .plugins .python .api .cfg .CfgBlock ;
27
29
import org .sonar .plugins .python .api .cfg .ControlFlowGraph ;
30
+ import org .sonar .plugins .python .api .symbols .Symbol ;
31
+ import org .sonar .plugins .python .api .symbols .Usage ;
28
32
import org .sonar .plugins .python .api .tree .AnnotatedAssignment ;
29
33
import org .sonar .plugins .python .api .tree .AssignmentStatement ;
30
34
import org .sonar .plugins .python .api .tree .Expression ;
31
35
import org .sonar .plugins .python .api .tree .FunctionDef ;
32
36
import org .sonar .plugins .python .api .tree .Name ;
33
37
import org .sonar .plugins .python .api .tree .NumericLiteral ;
38
+ import org .sonar .plugins .python .api .tree .Token ;
34
39
import org .sonar .plugins .python .api .tree .Tree ;
35
40
import org .sonar .plugins .python .api .tree .UnaryExpression ;
36
41
import org .sonar .python .cfg .fixpoint .LiveVariablesAnalysis ;
37
- import org .sonar .plugins .python .api .symbols .Symbol ;
38
- import org .sonar .plugins .python .api .symbols .Usage ;
42
+ import org .sonar .python .quickfix .IssueWithQuickFix ;
43
+ import org .sonar .python .quickfix .PythonQuickFix ;
44
+ import org .sonar .python .quickfix .PythonTextEdit ;
39
45
import org .sonar .python .tree .TreeUtils ;
40
46
41
47
import static org .sonar .python .checks .DeadStoreUtils .isUsedInSubFunction ;
48
+ import static org .sonar .python .quickfix .PythonTextEdit .remove ;
42
49
43
50
@ Rule (key = "S1854" )
44
51
public class DeadStoreCheck extends PythonSubscriptionCheck {
@@ -65,15 +72,21 @@ public void initialize(Context context) {
65
72
* Bottom-up approach, keeping track of which variables will be read by successor elements.
66
73
*/
67
74
private static void verifyBlock (SubscriptionContext ctx , CfgBlock block , LiveVariablesAnalysis .LiveVariables blockLiveVariables ,
68
- Set <Symbol > readSymbols , FunctionDef functionDef ) {
75
+ Set <Symbol > readSymbols , FunctionDef functionDef ) {
69
76
70
77
DeadStoreUtils .findUnnecessaryAssignments (block , blockLiveVariables , functionDef )
71
78
.stream ()
72
79
// symbols should have at least one read usage (otherwise will be reported by S1481)
73
80
.filter (unnecessaryAssignment -> readSymbols .contains (unnecessaryAssignment .symbol ))
74
81
.filter ((unnecessaryAssignment -> !isException (unnecessaryAssignment .symbol , unnecessaryAssignment .element , functionDef )))
75
- .forEach (unnecessaryAssignment ->
76
- ctx .addIssue (unnecessaryAssignment .element , String .format (MESSAGE_TEMPLATE , unnecessaryAssignment .symbol .name ())));
82
+ .forEach (unnecessaryAssignment -> {
83
+ Tree element = unnecessaryAssignment .element ;
84
+ String message = String .format (MESSAGE_TEMPLATE , unnecessaryAssignment .symbol .name ());
85
+ IssueWithQuickFix issue = (IssueWithQuickFix ) separatorToken (element )
86
+ .map (separator -> ctx .addIssue (element .firstToken (), separator , message ))
87
+ .orElseGet (() -> ctx .addIssue (element , message ));
88
+ createQuickFix (issue , unnecessaryAssignment .element );
89
+ });
77
90
}
78
91
79
92
private static boolean isMultipleAssignement (Tree element ) {
@@ -138,4 +151,38 @@ private static boolean isNumericLiteralOne(Expression expression) {
138
151
private static boolean isFunctionDeclarationSymbol (Symbol symbol ) {
139
152
return symbol .usages ().stream ().anyMatch (u -> u .kind () == Usage .Kind .FUNC_DECLARATION );
140
153
}
154
+
155
+ private static Optional <Token > separatorToken (Tree element ) {
156
+ if (element instanceof AssignmentStatement ) {
157
+ Token seperator = ((AssignmentStatement ) element ).separator ();
158
+ return "\n " .equals (seperator .value ()) ? Optional .empty () : Optional .of (seperator );
159
+ }
160
+ return Optional .empty ();
161
+ }
162
+
163
+ private static PythonTextEdit removeDeadStore (Tree tree ) {
164
+ List <Tree > childrenOfParent = tree .parent ().children ();
165
+ if (childrenOfParent .size () == 1 ) {
166
+ return remove (tree );
167
+ }
168
+ Token current = tree .firstToken ();
169
+ int i = childrenOfParent .indexOf (tree );
170
+ if (i == childrenOfParent .size () - 1 ) {
171
+ Token previous = childrenOfParent .get (i - 1 ).lastToken ();
172
+ current = tree .lastToken ();
173
+ // Replace from the end of the previous token (will also remove the separator and trailing whitespaces) until the end of the current token
174
+ return new PythonTextEdit ("" , previous .line (), previous .column () + previous .value ().length (), current .line (), current .column () + current .value ().length ());
175
+ }
176
+ Token next = childrenOfParent .get (i + 1 ).firstToken ();
177
+ // Remove from the start of the current tokenuntil the next token
178
+ return new PythonTextEdit ("" , current .line (), current .column (), next .line (), next .column ());
179
+ }
180
+
181
+ private static void createQuickFix (IssueWithQuickFix issue , Tree unnecessaryAssignment ) {
182
+ PythonTextEdit edit = removeDeadStore (unnecessaryAssignment );
183
+ PythonQuickFix quickFix = PythonQuickFix .newQuickFix ("Remove the line(s)" )
184
+ .addTextEdit (edit )
185
+ .build ();
186
+ issue .addQuickFix (quickFix );
187
+ }
141
188
}
0 commit comments