diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java index 5028f1cd630f..0e71b4003101 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java @@ -7,6 +7,7 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper; +import com.intellij.core.JavaPsiBundle; import com.intellij.modcommand.ModCommandAction; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.NlsContexts; @@ -111,6 +112,13 @@ public class SwitchBlockHighlightingModel { } else if (element instanceof PsiStatement statement) { if (enhancedLabels) { + //let's not highlight twice + if (statement instanceof PsiSwitchLabelStatement labelStatement && + labelStatement.getChildren().length != 0 && + labelStatement.getChildren()[labelStatement.getChildren().length - 1] instanceof PsiErrorElement errorElement && + errorElement.getErrorDescription().startsWith(JavaPsiBundle.message("expected.colon.or.arrow"))) { + break; + } alien = statement; break; } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorQuickFixProvider.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorQuickFixProvider.java index fe0da8e3dced..6990862b810b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorQuickFixProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorQuickFixProvider.java @@ -11,6 +11,7 @@ import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.core.JavaPsiBundle; import com.intellij.lang.java.JavaLanguage; import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -31,6 +32,27 @@ public final class JavaErrorQuickFixProvider implements ErrorQuickFixProvider { registrar.add(new AddExceptionToCatchFix(false).asIntention()); registrar.add(new AddFinallyFix((PsiTryStatement)parent).asIntention()); } + if (parent instanceof PsiSwitchLabelStatementBase && description.equals(JavaPsiBundle.message("expected.colon.or.arrow"))) { + PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(parent, PsiSwitchBlock.class); + if (switchBlock != null && switchBlock.getBody() != null) { + boolean isOld = false; + boolean isRule = false; + for (@NotNull PsiElement child : switchBlock.getBody().getChildren()) { + if (child instanceof PsiSwitchLabeledRuleStatement) { + isRule = true; + } + if (child instanceof PsiSwitchLabelStatement && !PsiTreeUtil.isAncestor(child, parent, false)) { + isOld = true; + } + } + if (isOld) { + info.registerFix(new InsertMissingTokenFix(":", true), null, null, null, null); + } + if (isRule) { + info.registerFix(new InsertMissingTokenFix(" ->", true), null, null, null, null); + } + } + } if (parent instanceof PsiSwitchLabeledRuleStatement && description.equals(JavaPsiBundle.message("expected.switch.rule"))) { IntentionAction action = QuickFixFactory.getInstance().createWrapSwitchRuleStatementsIntoBlockFix((PsiSwitchLabeledRuleStatement)parent); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertMissingTokenFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertMissingTokenFix.java index e70a32358eda..9981c814ca26 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertMissingTokenFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertMissingTokenFix.java @@ -15,10 +15,17 @@ import java.util.List; import java.util.Objects; public class InsertMissingTokenFix implements ModCommandAction { + @NotNull private final String myToken; + private final boolean myMoveAfter; - public InsertMissingTokenFix(String token) { + public InsertMissingTokenFix(@NotNull String token) { + this(token, false); + } + + public InsertMissingTokenFix(@NotNull String token, boolean moveAfter) { myToken = token; + myMoveAfter = moveAfter; } @Override @@ -44,7 +51,11 @@ public class InsertMissingTokenFix implements ModCommandAction { String oldText = document.getText(); String newText = oldText.substring(0, offset) + myToken + oldText.substring(offset); VirtualFile file = Objects.requireNonNull(FileDocumentManager.getInstance().getFile(document)); - return new ModUpdateFileText(file, oldText, newText, - List.of(new ModUpdateFileText.Fragment(offset, 0, myToken.length()))); + ModCommand fix = new ModUpdateFileText(file, oldText, newText, + List.of(new ModUpdateFileText.Fragment(offset, 0, myToken.length()))); + if (myMoveAfter) { + fix = fix.andThen(new ModNavigate(file, -1, -1, offset + myToken.length())); + } + return fix; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitch.java new file mode 100644 index 000000000000..ade78820ca96 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitch.java @@ -0,0 +1,61 @@ +class IncompleteSwitch { + + + public void testStatement(char o) { + switch (o) { + case + } + switch (o) { + case '1' + } + switch (o) { + case '1' when + } + switch (o) { + case char a when a == '1' + } + + + switch (o) { + case '2' -> System.out.println("1"); + case + } + switch (o) { + case '2' -> System.out.println("1"); + case '1' + } + switch (o) { + case '2' -> System.out.println("1"); + case '1' when + } + switch (o) { + case '2' -> System.out.println("1"); + case char a when a == '1' + } + } + + public void testExpression(char o) { + + int i = switch (o) { + case '2': + yield 1; + case + }; + + i = switch (o) { + case '2': + yield 2; + case '1' + }; + i = switch (o) { + case '2': + yield 2; + case '1' when + }; + i = switch (o) { + case '2': + yield 2; + case char a when a == '1' + }; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixArray.java new file mode 100644 index 000000000000..02d94387acb8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixArray.java @@ -0,0 +1,10 @@ +class IncompleteSwitchFixColon { + + public void testStatement(char o) { + + switch (o) { + case '2'-> System.out.println("1"); + case '1' + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixArray_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixArray_after.java new file mode 100644 index 000000000000..af3fd2edce35 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixArray_after.java @@ -0,0 +1,10 @@ +class IncompleteSwitchFixColon { + + public void testStatement(char o) { + + switch (o) { + case '2'-> System.out.println("1"); + case '1' -> + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixColon.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixColon.java new file mode 100644 index 000000000000..370f9b10e644 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixColon.java @@ -0,0 +1,12 @@ +class IncompleteSwitchFixColon { + + public void testStatement(char o) { + + switch (o) { + case '2': + System.out.println("1"); + break; + case '1' + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixColon_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixColon_after.java new file mode 100644 index 000000000000..7ead456c0b6a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/switchExpressions/IncompleteSwitchFixColon_after.java @@ -0,0 +1,12 @@ +class IncompleteSwitchFixColon { + + public void testStatement(char o) { + + switch (o) { + case '2': + System.out.println("1"); + break; + case '1': + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavaSwitchExpressionsHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavaSwitchExpressionsHighlightingTest.kt index ee3341960183..09ecbd58c9b0 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavaSwitchExpressionsHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavaSwitchExpressionsHighlightingTest.kt @@ -1,11 +1,14 @@ -// Copyright 2000-2019 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. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.codeInsight.daemon import com.intellij.JavaTestUtil +import com.intellij.codeInsight.daemon.impl.quickfix.InsertMissingTokenFix import com.intellij.codeInspection.redundantCast.RedundantCastInspection import com.intellij.pom.java.LanguageLevel import com.intellij.testFramework.IdeaTestUtil import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_15 +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.assertEquals class JavaSwitchExpressionsHighlightingTest : LightJavaCodeInsightFixtureTestCase() { override fun getProjectDescriptor() = JAVA_15 @@ -32,6 +35,26 @@ class JavaSwitchExpressionsHighlightingTest : LightJavaCodeInsightFixtureTestCas myFixture.enableInspections(RedundantCastInspection()) doTest() } + fun testIncompleteSwitch() = IdeaTestUtil.withLevel(module, LanguageLevel.JDK_21) { doTest() } + fun testIncompleteSwitchFixColon() = IdeaTestUtil.withLevel(module, LanguageLevel.JDK_21) { + doTest() + val availableIntentions = myFixture.availableIntentions + .mapNotNull { it.asModCommandAction() } + .filter { it is InsertMissingTokenFix } + assertEquals(1, availableIntentions.size) + myFixture.launchAction(availableIntentions.first().asIntention()) + myFixture.checkResultByFile("${getTestName(false)}_after.java") + } + + fun testIncompleteSwitchFixArray() = IdeaTestUtil.withLevel(module, LanguageLevel.JDK_21) { + doTest() + val availableIntentions = myFixture.availableIntentions + .mapNotNull { it.asModCommandAction() } + .filter { it is InsertMissingTokenFix } + assertEquals(1, availableIntentions.size) + myFixture.launchAction(availableIntentions.first().asIntention()) + myFixture.checkResultByFile("${getTestName(false)}_after.java") + } private fun doTest() { myFixture.configureByFile("${getTestName(false)}.java")