mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java][switch completion] IDEA-270439 Code completion for pattern matching in switch
Reuse the SealedUtils#findSameFileInheritorsClasses method in order to eliminate the duplicating code and reduce the searching scope. Enable "return" when there is a lambda in a switch label GitOrigin-RevId: 6e6dbcb8d9b1b16828fcdd29c1e59b2f47a71bb5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c0b2f613a4
commit
5ecc0aec9c
@@ -20,7 +20,6 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.filters.FilterPositionUtil;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
@@ -29,11 +28,15 @@ import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import com.siyeh.ig.psiutils.SealedUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.openapi.util.Conditions.notInstanceOf;
|
||||
import static com.intellij.patterns.PsiJavaPatterns.*;
|
||||
@@ -216,7 +219,7 @@ public class JavaKeywordCompletion {
|
||||
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ASSERT), TailType.INSERT_SPACE));
|
||||
}
|
||||
|
||||
if (!psiElement().inside(PsiSwitchExpression.class).accepts(myPosition)) {
|
||||
if (!psiElement().inside(PsiSwitchExpression.class).accepts(myPosition) || psiElement().inside(PsiLambdaExpression.class).accepts(myPosition)) {
|
||||
TailType returnTail = getReturnTail(myPosition);
|
||||
LookupElement ret = createKeyword(PsiKeyword.RETURN);
|
||||
if (returnTail != TailType.NONE) {
|
||||
@@ -294,6 +297,12 @@ public class JavaKeywordCompletion {
|
||||
private void addCaseNullToSwitch() {
|
||||
if (!isInsideCaseLabel()) return;
|
||||
|
||||
final PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(myPosition, PsiSwitchBlock.class, false, PsiMember.class);
|
||||
if (switchBlock == null) return;
|
||||
|
||||
final PsiType selectorType = getSelectorType(switchBlock);
|
||||
if (selectorType instanceof PsiPrimitiveType) return;
|
||||
|
||||
addKeyword(createKeyword(PsiKeyword.NULL));
|
||||
}
|
||||
|
||||
@@ -375,47 +384,39 @@ public class JavaKeywordCompletion {
|
||||
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
|
||||
if (switchBlock == null) return;
|
||||
|
||||
final PsiExpression selector = switchBlock.getExpression();
|
||||
if (selector == null) return;
|
||||
|
||||
final PsiType selectorType = selector.getType();
|
||||
final PsiType selectorType = getSelectorType(switchBlock);
|
||||
if (selectorType == null || selectorType instanceof PsiPrimitiveType) return;
|
||||
|
||||
final TailType caseRuleTail = TailTypes.forSwitchLabel(switchBlock);
|
||||
addKeyword(createCaseRule(PsiKeyword.NULL, caseRuleTail));
|
||||
addKeyword(createCaseRule(PsiKeyword.DEFAULT, caseRuleTail));
|
||||
|
||||
addSealedHierarchyCases(selector, caseRuleTail);
|
||||
addSealedHierarchyCases(selectorType);
|
||||
}
|
||||
|
||||
private void addSealedHierarchyCases(@Nullable PsiExpression expression, @NotNull TailType caseRuleTail) {
|
||||
if (expression == null) return;
|
||||
@Contract(pure = true)
|
||||
private static @Nullable PsiType getSelectorType(@NotNull PsiSwitchBlock switchBlock) {
|
||||
|
||||
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
|
||||
if (aClass == null || aClass.isEnum()) return;
|
||||
final PsiExpression selector = switchBlock.getExpression();
|
||||
if (selector == null) return null;
|
||||
|
||||
if (!aClass.hasModifierProperty(PsiModifier.SEALED)) return;
|
||||
return selector.getType();
|
||||
}
|
||||
|
||||
final PsiReferenceList list = aClass.getPermitsList();
|
||||
final StreamEx<String> inheritors = list != null ? getFromPermittedList(list) : getFromHierarchy(aClass);
|
||||
private void addSealedHierarchyCases(@NotNull PsiType type) {
|
||||
final PsiResolveHelper resolver = JavaPsiFacade.getInstance(myPosition.getProject()).getResolveHelper();
|
||||
|
||||
for (String inheritor : inheritors) {
|
||||
final LookupElement rule = createCaseRule(inheritor, caseRuleTail);
|
||||
addKeyword(rule);
|
||||
final PsiClass aClass = resolver.resolveReferencedClass(type.getCanonicalText(), null);
|
||||
if (aClass == null || aClass.isEnum() || !aClass.hasModifierProperty(PsiModifier.SEALED)) return;
|
||||
|
||||
for (PsiClass inheritor : SealedUtils.findSameFileInheritorsClasses(aClass)) {
|
||||
final JavaPsiClassReferenceElement item = AllClassesGetter.createLookupItem(inheritor, AllClassesGetter.TRY_SHORTENING);
|
||||
item.setForcedPresentableName("case " + inheritor.getName());
|
||||
addKeyword(item);
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull StreamEx<String> getFromHierarchy(@NotNull PsiClass aClass) {
|
||||
final Collection<PsiClass> inheritors = DirectClassInheritorsSearch.search(aClass).findAll();
|
||||
return StreamEx.of(inheritors).map(PsiNamedElement::getName);
|
||||
}
|
||||
|
||||
private static @NotNull StreamEx<String> getFromPermittedList(@NotNull PsiReferenceList list) {
|
||||
final PsiJavaCodeReferenceElement[] elements = list.getReferenceElements();
|
||||
return StreamEx.of(elements).map(PsiQualifiedReference::getReferenceName);
|
||||
}
|
||||
|
||||
private static @NotNull LookupElement createCaseRule(String caseRuleName, TailType tailType) {
|
||||
private static @NotNull LookupElement createCaseRule(@NotNull String caseRuleName, TailType tailType) {
|
||||
final String prefix = "case ";
|
||||
|
||||
final LookupElement lookupElement = LookupElementBuilder
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
class Main {
|
||||
|
||||
private static interface Empty {
|
||||
void f();
|
||||
}
|
||||
|
||||
private Empty f(Object o) {
|
||||
return switch (o) {
|
||||
default -> () -> {
|
||||
retu<caret>
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private Empty g(Object o1, Object o2) {
|
||||
return switch (o1) {
|
||||
default -> () -> {
|
||||
switch (o2) {
|
||||
case null -> { retu<caret> }
|
||||
case default -> { retu<caret> }
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
|
||||
class Main {
|
||||
|
||||
private static interface Empty {
|
||||
void f();
|
||||
}
|
||||
|
||||
private Empty f(Object o) {
|
||||
return switch (o) {
|
||||
default -> () -> {
|
||||
return;<caret>
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private Empty g(Object o1, Object o2) {
|
||||
return switch (o1) {
|
||||
default -> () -> {
|
||||
switch (o2) {
|
||||
case null -> { return;<caret> }
|
||||
case default -> { return;<caret> }
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -5,8 +5,8 @@ class Main {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Sealed permits Variant1, Variant2 { }
|
||||
final class Variant1 implements Sealed { }
|
||||
final class Variant2 implements Sealed { }
|
||||
private static sealed interface Sealed permits Variant1, Variant2 { }
|
||||
private static final class Variant1 implements Sealed { }
|
||||
private static final class Variant2 implements Sealed { }
|
||||
}
|
||||
+4
-4
@@ -5,8 +5,8 @@ class Main {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Sealed permits Variant1, Variant2 { }
|
||||
final class Variant1 implements Sealed { }
|
||||
final class Variant2 implements Sealed { }
|
||||
private static sealed interface Sealed permits Variant1, Variant2 { }
|
||||
private static final class Variant1 implements Sealed { }
|
||||
private static final class Variant2 implements Sealed { }
|
||||
}
|
||||
|
||||
+2
@@ -26,6 +26,8 @@ class NormalSwitchCompletionTest extends NormalCompletionTestCase {
|
||||
void testCompletePatternVariableInSwitchExpr() { doTest() }
|
||||
void testCompletePatternVariableInSwitchStmt() { doTest() }
|
||||
|
||||
void testCompleteReturnInSwitch() { doTest() }
|
||||
|
||||
void testCompleteConstantInSwitchExpr() { doTest() }
|
||||
void testCompleteConstantInSwitchStmt() { doTest() }
|
||||
|
||||
|
||||
+5
@@ -5,6 +5,7 @@ import com.intellij.JavaTestUtil
|
||||
import com.intellij.codeInsight.completion.CompletionType
|
||||
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import com.intellij.testFramework.NeedsIndex
|
||||
import groovy.transform.CompileStatic
|
||||
import org.jetbrains.annotations.NotNull
|
||||
|
||||
@@ -28,7 +29,11 @@ class NormalSwitchCompletionVariantsTest extends LightFixtureCompletionTestCase
|
||||
void testCompletionPrimitiveTypeStmt() { doTest(COMMON_VARIANTS) }
|
||||
void testCompletionVariantsInStmt() { doTest(COMMON_OBJECT_VARIANTS) }
|
||||
void testCompletionVariantsInExpr() { doTest(COMMON_OBJECT_VARIANTS) }
|
||||
|
||||
@NeedsIndex.Full
|
||||
void testCompletionSealedHierarchyStmt() { doTest(COMMON_OBJECT_VARIANTS + ["case Variant1", "case Variant2"]) }
|
||||
|
||||
@NeedsIndex.Full
|
||||
void testCompletionSealedHierarchyExpr() { doTest(COMMON_OBJECT_VARIANTS + ["case Variant1", "case Variant2"]) }
|
||||
|
||||
private void doTest(String[] variants) {
|
||||
|
||||
+13
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2020 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-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.siyeh.ig.psiutils;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.PropertyKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
@@ -55,11 +56,21 @@ public final class SealedUtils {
|
||||
.anyMatch(parent -> parent != null && parent.hasModifierProperty(PsiModifier.SEALED));
|
||||
}
|
||||
|
||||
public static Collection<PsiClass> findSameFileInheritorsClasses(@NotNull PsiClass psiClass, PsiClass @NotNull ... classesToExclude) {
|
||||
return getClasses(psiClass, Function.identity(), classesToExclude);
|
||||
}
|
||||
|
||||
public static Collection<String> findSameFileInheritors(@NotNull PsiClass psiClass, PsiClass @NotNull ... classesToExclude) {
|
||||
return getClasses(psiClass, PsiClass::getQualifiedName, classesToExclude);
|
||||
}
|
||||
|
||||
private static @NotNull <T> Collection<T> getClasses(@NotNull PsiClass psiClass,
|
||||
Function<PsiClass, T> mapper,
|
||||
PsiClass @NotNull ... classesToExclude) {
|
||||
GlobalSearchScope fileScope = GlobalSearchScope.fileScope(psiClass.getContainingFile());
|
||||
return DirectClassInheritorsSearch.search(psiClass, fileScope)
|
||||
.filtering(inheritor -> !ArrayUtil.contains(inheritor, classesToExclude))
|
||||
.mapping(inheritor -> inheritor.getQualifiedName())
|
||||
.mapping(mapper)
|
||||
.findAll();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user