DeleteSwitchLabelFix: delete unreachable switch branch

Fixes IDEA-199692 Quickfix to delete unreachable switch branch reported by "constant conditions & exceptions"
This commit is contained in:
Tagir Valeev
2018-10-01 14:54:36 +07:00
parent 27b2efd106
commit 5fc3612fa9
12 changed files with 267 additions and 5 deletions
@@ -8,10 +8,7 @@ import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.DataFlowInstructionVisitor.ConstantResult;
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind.NullabilityProblem;
import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix;
import com.intellij.codeInspection.dataFlow.fix.ReplaceWithConstantValueFix;
import com.intellij.codeInspection.dataFlow.fix.ReplaceWithObjectsEqualsFix;
import com.intellij.codeInspection.dataFlow.fix.SimplifyToAssignmentFix;
import com.intellij.codeInspection.dataFlow.fix.*;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.nullable.NullableStuffInspectionBase;
@@ -693,7 +690,8 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
else if (psiAnchor instanceof PsiSwitchLabelStatement) {
if (falseSet.contains(instruction)) {
holder.registerProblem(psiAnchor,
InspectionsBundle.message("dataflow.message.unreachable.switch.label"));
InspectionsBundle.message("dataflow.message.unreachable.switch.label"),
new DeleteSwitchLabelFix((PsiSwitchLabelStatement)psiAnchor));
}
}
else if (psiAnchor != null && !isFlagCheck(psiAnchor)) {
@@ -0,0 +1,97 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.dataFlow.fix;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
public class DeleteSwitchLabelFix implements LocalQuickFix {
private final String myName;
private final boolean myBranch;
public DeleteSwitchLabelFix(PsiSwitchLabelStatement label) {
myName = Objects.requireNonNull(label.getCaseValue()).getText();
PsiStatement nextStatement = PsiTreeUtil.getNextSiblingOfType(label, PsiStatement.class);
if (nextStatement instanceof PsiSwitchLabelStatement) {
myBranch = false;
}
else {
PsiStatement prevStatement = PsiTreeUtil.getPrevSiblingOfType(label, PsiStatement.class);
myBranch = prevStatement == null || !ControlFlowUtils.statementMayCompleteNormally(prevStatement);
}
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
return myBranch ?
"Remove switch branch '" + myName + "'" :
"Remove switch label '" + myName + "'";
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return "Remove switch label";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiSwitchLabelStatement label = PsiTreeUtil.getNonStrictParentOfType(descriptor.getStartElement(), PsiSwitchLabelStatement.class);
if (label == null) return;
if (myBranch) {
PsiCodeBlock scope = ObjectUtils.tryCast(label.getParent(), PsiCodeBlock.class);
if (scope == null) return;
PsiSwitchLabelStatement nextLabel = PsiTreeUtil.getNextSiblingOfType(label, PsiSwitchLabelStatement.class);
PsiElement stopAt = nextLabel == null ? scope.getRBrace() : nextLabel;
while(true) {
PsiStatement next = PsiTreeUtil.getNextSiblingOfType(nextLabel, PsiStatement.class);
if (!(next instanceof PsiSwitchLabelStatement)) break;
nextLabel = (PsiSwitchLabelStatement)next;
}
int end = nextLabel == null ? -1 : nextLabel.getTextOffset();
List<PsiElement> toDelete = new ArrayList<>();
List<PsiDeclarationStatement> declarations = new ArrayList<>();
for (PsiElement e = label.getNextSibling(); e != stopAt; e = e.getNextSibling()) {
if (e instanceof PsiDeclarationStatement && nextLabel != null) {
PsiDeclarationStatement declaration = (PsiDeclarationStatement)e;
PsiElement[] elements = declaration.getDeclaredElements();
boolean declarationIsReused = Stream.of(elements).anyMatch(
element -> !ReferencesSearch.search(element, new LocalSearchScope(scope))
.forEach(ref -> ref.getElement().getTextOffset() < end));
if (declarationIsReused) {
StreamEx.of(elements).select(PsiVariable.class).map(PsiVariable::getInitializer).nonNull().into(toDelete);
declarations.add(declaration);
continue;
}
}
toDelete.add(e);
}
CommentTracker ct = new CommentTracker();
toDelete.stream().filter(PsiElement::isValid).forEach(ct::delete);
for (PsiDeclarationStatement declaration : declarations) {
scope.addAfter(declaration, nextLabel);
declaration.delete();
}
ct.insertCommentsBefore(label);
}
new CommentTracker().deleteAndRestoreComments(label);
}
}
@@ -0,0 +1,16 @@
// "Remove switch branch '"baz"'" "true"
class Main {
public void test() {
switch ("foo") {
case "qux":
case "bar":
class Foo {}
int i;
i = 2;
System.out.println("hello"+new Foo()+i);
//oops
default:
System.out.println("oops");
}
}
}
@@ -0,0 +1,19 @@
// "Remove switch label '"qux"'" "true"
class Main {
public void test() {
switch ("foo") {
case "baz":
int i = 1;
class Foo {}
System.out.println(i);
break;
/*comment*/
case "bar":
i = 2;
System.out.println("hello"+new Foo()+i);
//oops
default:
System.out.println("oops");
}
}
}
@@ -0,0 +1,19 @@
// "Remove switch label '"bar"'" "true"
class Main {
public void test() {
switch ("foo") {
case "baz":
int i = 1;
class Foo {}
System.out.println(i);
break;
case "qux":
// comment
i = 2;
System.out.println("hello"+new Foo()+i);
//oops
default:
System.out.println("oops");
}
}
}
@@ -0,0 +1,11 @@
// "Remove switch branch '"bar"'" "true"
class Main {
public void test() {
switch ("foo") {
case "baz":
System.out.println("foo");
break;
//oops
}
}
}
@@ -0,0 +1,19 @@
// "Remove switch branch '"baz"'" "true"
class Main {
public void test() {
switch ("foo") {
case "<caret>baz":
int i = 1;
class Foo {}
System.out.println(i);
break;
case "qux":
case "bar":
i = 2;
System.out.println("hello"+new Foo()+i);
//oops
default:
System.out.println("oops");
}
}
}
@@ -0,0 +1,19 @@
// "Remove switch label '"qux"'" "true"
class Main {
public void test() {
switch ("foo") {
case "baz":
int i = 1;
class Foo {}
System.out.println(i);
break;
case "<caret>qux" /*comment*/:
case "bar":
i = 2;
System.out.println("hello"+new Foo()+i);
//oops
default:
System.out.println("oops");
}
}
}
@@ -0,0 +1,20 @@
// "Remove switch label '"bar"'" "true"
class Main {
public void test() {
switch ("foo") {
case "baz":
int i = 1;
class Foo {}
System.out.println(i);
break;
case "qux":
case "<caret>bar" // comment
:
i = 2;
System.out.println("hello"+new Foo()+i);
//oops
default:
System.out.println("oops");
}
}
}
@@ -0,0 +1,15 @@
// "Remove switch branch '"bar"'" "true"
class Main {
public void test() {
switch ("foo") {
case "baz":
System.out.println("foo");
break;
case "<caret>bar":
int i = 2;
class Foo {}
System.out.println("hello"+new Foo()+i);
//oops
}
}
}
@@ -0,0 +1,28 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class DeleteSwitchLabelFixTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new DataFlowInspection()};
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return LightCodeInsightFixtureTestCase.JAVA_8;
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel";
}
}
@@ -65,6 +65,7 @@ import org.junit.runners.Suite;
StreamFilterNotNullFixTest.class,
RedundantInstanceofFixTest.class,
ReplaceComputeWithComputeIfPresentFixTest.class,
DeleteSwitchLabelFixTest.class,
})
public class DataFlowInspectionTestSuite {
}