diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java index b62590830865..9dc93299d6c0 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java @@ -1,46 +1,44 @@ -/* - * Copyright 2000-2013 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.codeInsight.completion; import com.intellij.application.options.CodeStyle; import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature; import com.intellij.codeInsight.lookup.*; import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer; import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.util.ClassConditionKey; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.codeStyle.VariableKind; import com.intellij.psi.impl.source.resolve.JavaResolveUtil; import com.intellij.psi.util.PsiFormatUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.TypeUtils; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; +import java.util.Locale; import java.util.Set; /** * @author peter */ public class JavaPsiClassReferenceElement extends LookupItem implements TypedLookupItem { + private static final Logger LOGGER = Logger.getInstance(JavaPsiClassReferenceElement.class.getName()); + public static final ClassConditionKey CLASS_CONDITION_KEY = ClassConditionKey.create(JavaPsiClassReferenceElement.class); private final SmartPsiElementPointer myClass; private final String myQualifiedName; + private final String myClassName; private String myForcedPresentableName; private final String myPackageDisplayName; private PsiSubstitutor mySubstitutor = PsiSubstitutor.EMPTY; @@ -48,6 +46,7 @@ public class JavaPsiClassReferenceElement extends LookupItem implements public JavaPsiClassReferenceElement(PsiClass psiClass) { super(psiClass.getName(), psiClass.getName()); myQualifiedName = psiClass.getQualifiedName(); + myClassName = psiClass.getName(); myClass = SmartPointerManager.getInstance(psiClass.getProject()).createSmartPsiElementPointer(psiClass); setInsertHandler(AllClassesGetter.TRY_SHORTENING); setTailType(TailType.NONE); @@ -232,4 +231,47 @@ public class JavaPsiClassReferenceElement extends LookupItem implements } return false; } + + @Override + public void handleInsert(@NotNull InsertionContext context) { + final PsiElement element = context.getFile().findElementAt(context.getStartOffset()); + if (element != null && + HighlightingFeature.PATTERNS_IN_SWITCH.isAvailable(element) && + JavaCompletionContributor.IN_SWITCH_LABEL.accepts(element)) { + + final String variableName = getVariableName(); + if (!variableName.isEmpty()) { + context.getDocument().insertString(context.getTailOffset(), " " + variableName); + + final CaretModel model = context.getEditor().getCaretModel(); + model.moveToOffset(context.getTailOffset()); + } + } + + super.handleInsert(context); + } + + @Contract(value = "-> !null", pure = true) + private String getVariableName() { + final PsiElement psi = getPsiElement(); + LOGGER.assertTrue(psi instanceof PsiClass, "Mismatched element type"); + + final String[] strings = getPossibleVariableNames((PsiClass)psi); + + if (strings.length > 0) return strings[0]; + + if (myClassName.isEmpty()) return ""; + + return myClassName.substring(0, 1).toLowerCase(Locale.ROOT); + } + + @Contract(pure = true) + private static String@NotNull [] getPossibleVariableNames(@NotNull PsiClass psi) { + final PsiClassType type = TypeUtils.getType(psi); + + final JavaCodeStyleManager instance = JavaCodeStyleManager.getInstance(psi.getProject()); + final SuggestedNameInfo info = instance.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, type); + + return JavaCompletionUtil.completeVariableNameForRefactoring(instance, type, VariableKind.LOCAL_VARIABLE, info); + } } diff --git a/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchExpr.java b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchExpr.java new file mode 100644 index 000000000000..420356c3f42c --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchExpr.java @@ -0,0 +1,8 @@ + +class Main { + int f(Object o) { + return switch(o) { + case Intege + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchExpr_after.java b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchExpr_after.java new file mode 100644 index 000000000000..87eddbb0330d --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchExpr_after.java @@ -0,0 +1,8 @@ + +class Main { + int f(Object o) { + return switch(o) { + case Integer integer -> + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchStmt.java b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchStmt.java new file mode 100644 index 000000000000..709959b5d429 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchStmt.java @@ -0,0 +1,8 @@ + +class Main { + void f(Object o) { + switch(o) { + case Intege + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchStmt_after.java b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchStmt_after.java new file mode 100644 index 000000000000..8bd25b72cf94 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/CompletePatternVariableInSwitchStmt_after.java @@ -0,0 +1,8 @@ + +class Main { + void f(Object o) { + switch(o) { + case Integer integer: + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalSwitchCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalSwitchCompletionTest.groovy index 0b770a26b293..b4f32625f6c7 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalSwitchCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalSwitchCompletionTest.groovy @@ -10,7 +10,7 @@ class NormalSwitchCompletionTest extends NormalCompletionTestCase { @NotNull @Override protected LightProjectDescriptor getProjectDescriptor() { - return JAVA_15 + return JAVA_17 } void testDefaultInRuleSwitch() { doTest() } @@ -22,4 +22,8 @@ class NormalSwitchCompletionTest extends NormalCompletionTestCase { void testInsideYieldInSwitchExpression() { doTest() } void testInsideRuleInSwitchExpression() { doTest() } void testBreakDeepInsideSwitchExpression() { doTest() } + + void testCompletePatternVariableInSwitchExpr() { doTest() } + void testCompletePatternVariableInSwitchStmt() { doTest() } + } \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java index 95a3900fabdb..ec2ca6ac8e7d 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java @@ -128,7 +128,7 @@ public final class SwitchUtils { final PsiExpression[] operands = polyadicExpression.getOperands(); if (operation.equals(JavaTokenType.OROR)) { for (PsiExpression operand : operands) { - if (!canBeSwitchCase(operand, switchExpression, languageLevel, existingCaseValues, isPatternMatch)) { + if (!canBeSwitchCase(operand, switchExpression, languageLevel, existingCaseValues, false)) { return false; } } @@ -147,16 +147,36 @@ public final class SwitchUtils { /** * Returns true if given switch block has a rule-based format (like 'case 0 ->') - * @param block block to test + * @param block a switch block to test * @return true if given switch block has a rule-based format; false if it has conventional label-based format (like 'case 0:') - * If switch body has no labels yet and language level permits, rule-based format is assumed. + * If switch body has no labels yet and language level permits, + * the rule-based format is assumed if the switch block is of the {@link PsiSwitchExpression} type. */ public static boolean isRuleFormatSwitch(@NotNull PsiSwitchBlock block) { if (!HighlightingFeature.ENHANCED_SWITCH.isAvailable(block)) { return false; } + final PsiSwitchLabelStatementBase label = PsiTreeUtil.getChildOfType(block.getBody(), PsiSwitchLabelStatementBase.class); - return label == null || label instanceof PsiSwitchLabeledRuleStatement; + + if (label == null || isBeingCompletedSwitchLabel(label)) { + return block instanceof PsiSwitchExpression; + } + + return label instanceof PsiSwitchLabeledRuleStatement; + } + + /** + * Checks if the passed switch label is the one that is being completed + * (see {@link com.intellij.codeInsight.completion.CompletionProvider}). + * A switch label that is being completed is distinct from the other switch labels + * by the fact that the very last child of the label is a {@link PsiErrorElement} + * which notifies that the colon character ":" is expected. + * @param label a case label to check + * @return true if the passed case label is the one that is being completed right now. + */ + private static boolean isBeingCompletedSwitchLabel(@NotNull PsiSwitchLabelStatementBase label) { + return label.getLastChild() instanceof PsiErrorElement; } public static boolean canBeSwitchSelectorExpression(PsiExpression expression, LanguageLevel languageLevel) { diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/create_default/afterEmpty.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/create_default/afterEmpty.java index e19078719869..83b34c678a8f 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/create_default/afterEmpty.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/create_default/afterEmpty.java @@ -3,7 +3,8 @@ class X { void test(int i) { switch(i) { - default -> throw new IllegalStateException("Unexpected value: " + i); + default: + throw new IllegalStateException("Unexpected value: " + i); } } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterExpressionBreak.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterExpressionBreak.java index 55a417517f09..31a1a286501a 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterExpressionBreak.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterExpressionBreak.java @@ -6,12 +6,10 @@ class BeforeDefault { String test(X x) { return switch (x) { - case A: yield "foo"; - case B: - yield null; - case C: - yield null; - default: yield "bar"; + case A -> "foo"; + case B -> null; + case C -> null; + default -> "bar"; }; } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterWithoutBraces.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterWithoutBraces.java index f67966f120cf..c5d32a8969b2 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterWithoutBraces.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/afterWithoutBraces.java @@ -2,10 +2,10 @@ class Foo { void foo(E e) { switch (e) { - case E1 -> { - } - case E2 -> { - } + case E1: + break; + case E2: + break; } } } diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/beforeExpressionBreak.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/beforeExpressionBreak.java index 62c3c5bfa0e9..83a8cd197c92 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/beforeExpressionBreak.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/controlflow/enumswitch/beforeExpressionBreak.java @@ -6,8 +6,8 @@ class BeforeDefault { String test(X x) { return switch (x) { - case A: yield "foo"; - default: yield "bar"; + case A -> "foo"; + default -> "bar"; }; } } \ No newline at end of file