diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index e685d3797e31..54421e1bbd31 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -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)) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java new file mode 100644 index 000000000000..c137fff5972b --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java @@ -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 toDelete = new ArrayList<>(); + List 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); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteBranch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteBranch.java new file mode 100644 index 000000000000..137bdc6d8492 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteBranch.java @@ -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"); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteCase.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteCase.java new file mode 100644 index 000000000000..a27727df423f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteCase.java @@ -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"); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteCase2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteCase2.java new file mode 100644 index 000000000000..49aecec43263 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteCase2.java @@ -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"); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteLastBranch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteLastBranch.java new file mode 100644 index 000000000000..f98c7e268dc8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/afterDeleteLastBranch.java @@ -0,0 +1,11 @@ +// "Remove switch branch '"bar"'" "true" +class Main { + public void test() { + switch ("foo") { + case "baz": + System.out.println("foo"); + break; + //oops + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteBranch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteBranch.java new file mode 100644 index 000000000000..67c7ed6fade6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteBranch.java @@ -0,0 +1,19 @@ +// "Remove switch branch '"baz"'" "true" +class Main { + public void test() { + switch ("foo") { + case "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"); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteCase.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteCase.java new file mode 100644 index 000000000000..fa7abd99bc3f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteCase.java @@ -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 "qux" /*comment*/: + case "bar": + i = 2; + System.out.println("hello"+new Foo()+i); + //oops + default: + System.out.println("oops"); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteCase2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteCase2.java new file mode 100644 index 000000000000..81c81d37d186 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteCase2.java @@ -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 "bar" // comment + : + i = 2; + System.out.println("hello"+new Foo()+i); + //oops + default: + System.out.println("oops"); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteLastBranch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteLastBranch.java new file mode 100644 index 000000000000..fe4b3647579c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/deleteSwitchLabel/beforeDeleteLastBranch.java @@ -0,0 +1,15 @@ +// "Remove switch branch '"bar"'" "true" +class Main { + public void test() { + switch ("foo") { + case "baz": + System.out.println("foo"); + break; + case "bar": + int i = 2; + class Foo {} + System.out.println("hello"+new Foo()+i); + //oops + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/DeleteSwitchLabelFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/DeleteSwitchLabelFixTest.java new file mode 100644 index 000000000000..ae3c0f306aa2 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/DeleteSwitchLabelFixTest.java @@ -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"; + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java index 8d3a1ffb5c46..431192bbf52c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java @@ -65,6 +65,7 @@ import org.junit.runners.Suite; StreamFilterNotNullFixTest.class, RedundantInstanceofFixTest.class, ReplaceComputeWithComputeIfPresentFixTest.class, + DeleteSwitchLabelFixTest.class, }) public class DataFlowInspectionTestSuite { }