1919 */
2020package org .sonar .python .checks ;
2121
22- import java .util .List ;
23- import java .util .Optional ;
2422import java .util .Set ;
2523import javax .annotation .Nullable ;
2624import org .sonar .check .Rule ;
@@ -83,15 +81,25 @@ private static void verifyBlock(SubscriptionContext ctx, CfgBlock block, LiveVar
8381 // symbols should have at least one read usage (otherwise will be reported by S1481)
8482 .filter (unnecessaryAssignment -> readSymbols .contains (unnecessaryAssignment .symbol ))
8583 .filter ((unnecessaryAssignment -> !isException (unnecessaryAssignment .symbol , unnecessaryAssignment .element , functionDef )))
86- .forEach (unnecessaryAssignment -> {
87- Tree element = unnecessaryAssignment .element ;
88- String message = String .format (MESSAGE_TEMPLATE , unnecessaryAssignment .symbol .name ());
89- IssueWithQuickFix issue = (IssueWithQuickFix ) separatorTokenForIssue (element )
90- .map (separator -> ctx .addIssue (element .firstToken (), separator , message ))
91- .orElseGet (() -> ctx .addIssue (element , message ));
84+ .forEach (unnecessaryAssignment -> raiseIssue (ctx , unnecessaryAssignment ));
85+ }
86+
87+ private static void raiseIssue (SubscriptionContext ctx , DeadStoreUtils .UnnecessaryAssignment unnecessaryAssignment ) {
88+ Tree element = unnecessaryAssignment .element ;
89+ String message = String .format (MESSAGE_TEMPLATE , unnecessaryAssignment .symbol .name ());
90+ Token lastRelevantToken = TreeUtils .getTreeSeparatorOrLastToken (element );
91+ PreciseIssue issue ;
92+ if ("\n " .equals (lastRelevantToken .value ())) {
93+ issue = ctx .addIssue (element , message );
94+ } else {
95+ issue = ctx .addIssue (element .firstToken (), lastRelevantToken , message );
96+ }
97+
98+ if (element instanceof Statement && !isExceptionForQuickFix ((Statement ) element )) {
99+ ((IssueWithQuickFix ) issue ).addQuickFix (PythonQuickFix .newQuickFix ("Remove the unused statement" ,
100+ PythonTextEdit .removeStatement ((Statement ) element )));
101+ }
92102
93- createQuickFix (issue , element );
94- });
95103 }
96104
97105 private static boolean isMultipleAssignement (Tree element ) {
@@ -157,60 +165,21 @@ private static boolean isFunctionDeclarationSymbol(Symbol symbol) {
157165 return symbol .usages ().stream ().anyMatch (u -> u .kind () == Usage .Kind .FUNC_DECLARATION );
158166 }
159167
160- private static Optional <Token > separatorTokenForIssue (Tree element ) {
161- return separatorTokenOfElement (element )
162- .filter (sep -> !"\n " .equals (sep .value ()));
163- }
164-
165- private static Optional <Token > separatorTokenOfElement (Tree element ) {
166- if (element .is (Tree .Kind .ASSIGNMENT_STMT , Tree .Kind .EXPRESSION_STMT )) {
167- Token separator = ((Statement ) element ).separator ();
168- return Optional .ofNullable (separator );
169- }
170- return Optional .empty ();
171- }
172-
173- private static PythonTextEdit removeDeadStore (Tree currentTree ) {
174- Optional <Token > tokenSeparator = separatorTokenOfElement (currentTree );
175- List <Tree > childrenOfParent = currentTree .parent ().children ();
176-
177- if (childrenOfParent .size () == 1 ) {
178- return PythonTextEdit .replace (currentTree , "pass" );
179- }
180168
181- Token currentFirstToken = currentTree .firstToken ();
182- int indexOfCurrentTree = childrenOfParent .indexOf (currentTree );
183-
184- // If the tree to remove is the last one, we remove from the end of the previous tree until the end of the current one
185- if (indexOfCurrentTree == childrenOfParent .size () - 1 ) {
186- Tree previousTree = childrenOfParent .get (indexOfCurrentTree - 1 );
187- Optional <Token > previousSep = separatorTokenOfElement (previousTree );
188- Token previous = previousSep .orElse (previousTree .lastToken ());
189- currentFirstToken = tokenSeparator .orElse (currentTree .lastToken ());
190- return removeFromEndOfTillEndOf (previous , currentFirstToken );
191- }
192-
193- // If the next tree is more than 1 line after the currentFirstToken tree, we only remove the separator
194- Token next ;
195- int currentLine = currentTree .lastToken ().line ();
196- int nextLine = childrenOfParent .get (indexOfCurrentTree + 1 ).firstToken ().line ();
197- if (nextLine > currentLine + 1 ) {
198- next = tokenSeparator .orElse (currentTree .lastToken ());
199- // Remove from the start of the currentFirstToken token until after the separator
200- return new PythonTextEdit ("" , currentFirstToken .line (), currentFirstToken .column (), next .line (), next .column () + next .value ().length ());
201- } else {
202- // Remove from the start of the currentFirstToken token until the next token
203- next = childrenOfParent .get (indexOfCurrentTree + 1 ).firstToken ();
204- return new PythonTextEdit ("" , currentFirstToken .line (), currentFirstToken .column (), next .line (), next .column ());
205- }
206- }
207-
208- private static boolean hasPotentialSideEffect (Tree tree ) {
169+ private static boolean isExceptionForQuickFix (Statement tree ) {
209170 switch (tree .getKind ()) {
171+ // foo:str = bar
210172 case ANNOTATED_ASSIGNMENT :
211173 return SideEffectDetector .hasSideEffect (((AnnotatedAssignment ) tree ).assignedValue ());
174+ // foo = bar or foo = bar = 1
212175 case ASSIGNMENT_STMT :
176+ // TODO: SONARPY-1192 Provide quick fix for chained assignment
177+ AssignmentStatement assignmentStatement = (AssignmentStatement ) tree ;
178+ if (assignmentStatement .lhsExpressions ().size () > 1 ) {
179+ return true ;
180+ }
213181 return SideEffectDetector .hasSideEffect (((AssignmentStatement ) tree ).assignedValue ());
182+ // foo(bar:=3)
214183 case EXPRESSION_STMT :
215184 ExpressionStatement expressionStatement = (ExpressionStatement ) tree ;
216185 return expressionStatement .expressions ().stream ().anyMatch (SideEffectDetector ::hasSideEffect );
@@ -219,22 +188,6 @@ private static boolean hasPotentialSideEffect(Tree tree) {
219188 }
220189 }
221190
222- private static void createQuickFix (IssueWithQuickFix issue , Tree unnecessaryAssignment ) {
223- if (hasPotentialSideEffect (unnecessaryAssignment )) {
224- return ;
225- }
226- PythonTextEdit edit = removeDeadStore (unnecessaryAssignment );
227- PythonQuickFix quickFix = PythonQuickFix .newQuickFix ("Remove the unused statement" )
228- .addTextEdit (edit )
229- .build ();
230- issue .addQuickFix (quickFix );
231- }
232-
233- private static PythonTextEdit removeFromEndOfTillEndOf (Token first , Token last ) {
234- return new PythonTextEdit ("" , first .line (), first .column () + first .value ().length (),
235- last .line (), last .column () + last .value ().length ());
236- }
237-
238191 private static class SideEffectDetector extends BaseTreeVisitor {
239192
240193 private boolean sideEffect = false ;
0 commit comments