[java-completion] IDEA-333184 IJ-CR-115840 completion for patterns in switch

- added new tests
- filter cases after caret
- reuse constant

GitOrigin-RevId: 35065e3cdd795891bd20d9e7d5387de09cc48083
This commit is contained in:
Mikhail Pyltsin
2023-10-02 17:13:43 +02:00
committed by intellij-monorepo-bot
parent 31d6b6112c
commit e698c46083
6 changed files with 112 additions and 81 deletions

View File

@@ -46,7 +46,8 @@ import static com.intellij.psi.SyntaxTraverser.psiApi;
public class JavaKeywordCompletion {
public static final ElementPattern<PsiElement> AFTER_DOT = psiElement().afterLeaf(".");
static final ElementPattern<PsiElement> VARIABLE_AFTER_FINAL = psiElement().afterLeaf(PsiKeyword.FINAL).inside(PsiDeclarationStatement.class);
static final ElementPattern<PsiElement> VARIABLE_AFTER_FINAL =
psiElement().afterLeaf(PsiKeyword.FINAL).inside(PsiDeclarationStatement.class);
private static final ElementPattern<PsiElement> INSIDE_PARAMETER_LIST =
psiElement().withParent(
@@ -69,8 +70,6 @@ public class JavaKeywordCompletion {
context.commitDocument();
CodeStyleManager.getInstance(context.getProject()).adjustLineIndent(context.getFile(), context.getStartOffset());
};
private static final String NULL = "null";
private static final String DEFAULT = "default";
private static boolean isStatementCodeFragment(PsiFile file) {
return file instanceof JavaCodeFragment &&
@@ -121,7 +120,7 @@ public class JavaKeywordCompletion {
PsiKeyword.CHAR, PsiKeyword.BYTE
);
static final PsiElementPattern<PsiElement,?> START_FOR = psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for"));
static final PsiElementPattern<PsiElement, ?> START_FOR = psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for"));
private static final ElementPattern<PsiElement> CLASS_REFERENCE =
psiElement().withParent(psiReferenceExpression().referencing(psiClass().andNot(psiElement(PsiTypeParameter.class))));
@@ -174,13 +173,13 @@ public class JavaKeywordCompletion {
private static TailType getReturnTail(PsiElement position) {
PsiElement scope = position;
while(true){
if (scope instanceof PsiFile || scope instanceof PsiClassInitializer){
while (true) {
if (scope instanceof PsiFile || scope instanceof PsiClassInitializer) {
return TailType.NONE;
}
if (scope instanceof PsiMethod method){
if(method.isConstructor() || PsiTypes.voidType().equals(method.getReturnType())) {
if (scope instanceof PsiMethod method) {
if (method.isConstructor() || PsiTypes.voidType().equals(method.getReturnType())) {
return TailType.SEMICOLON;
}
@@ -223,7 +222,8 @@ public class JavaKeywordCompletion {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ASSERT), TailType.INSERT_SPACE));
}
if (!psiElement().inside(PsiSwitchExpression.class).accepts(myPosition) || psiElement().inside(PsiLambdaExpression.class).accepts(myPosition)) {
if (!psiElement().inside(PsiSwitchExpression.class).accepts(myPosition) ||
psiElement().inside(PsiLambdaExpression.class).accepts(myPosition)) {
addKeyword(createReturnKeyword());
}
@@ -311,6 +311,7 @@ public class JavaKeywordCompletion {
addCaseNullToSwitch();
}
void addEnhancedCases() {
if (!canAddKeywords()) return;
@@ -347,7 +348,8 @@ public class JavaKeywordCompletion {
return;
}
PsiPattern psiPattern = PsiTreeUtil.getParentOfType(myPrevLeaf, PsiPattern.class, true, PsiStatement.class, PsiMember.class, PsiClass.class);
PsiPattern psiPattern =
PsiTreeUtil.getParentOfType(myPrevLeaf, PsiPattern.class, true, PsiStatement.class, PsiMember.class, PsiClass.class);
if (psiPattern == null ||
(psiPattern instanceof PsiTypeTestPattern testPattern &&
testPattern.getPatternVariable() != null &&
@@ -430,7 +432,6 @@ public class JavaKeywordCompletion {
private boolean isInsideCaseLabel() {
if (!HighlightingFeature.PATTERNS_IN_SWITCH.isAvailable(myPosition)) return false;
return psiElement().withSuperParent(2, PsiCaseLabelElementList.class).accepts(myPosition);
}
private void addVar() {
@@ -459,7 +460,7 @@ public class JavaKeywordCompletion {
PsiParameterList paramList = PsiTreeUtil.getParentOfType(position, PsiParameterList.class);
if (paramList != null && paramList.getParent() instanceof PsiLambdaExpression) {
PsiParameter param = PsiTreeUtil.getParentOfType(position, PsiParameter.class);
PsiTypeElement type = param == null ? null :param.getTypeElement();
PsiTypeElement type = param == null ? null : param.getTypeElement();
return type == null || PsiTreeUtil.isAncestor(type, position, false);
}
return false;
@@ -483,7 +484,8 @@ public class JavaKeywordCompletion {
assert myPrevLeaf != null;
if (myPrevLeaf.getParent().getParent() instanceof PsiAnnotationMethod) {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.HUMBLE_SPACE_BEFORE_WORD));
} else {
}
else {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.THROWS), TailType.HUMBLE_SPACE_BEFORE_WORD));
}
}
@@ -511,10 +513,10 @@ public class JavaKeywordCompletion {
if (selectorType == null || selectorType instanceof PsiPrimitiveType) return;
final TailType caseRuleTail = TailTypes.forSwitchLabel(switchBlock);
Set<String> containedLabels = getSwitchCoveredLabels(switchBlock);
if(!containedLabels.contains(NULL)){
Set<String> containedLabels = getSwitchCoveredLabels(switchBlock, myPosition);
if (!containedLabels.contains(PsiKeyword.NULL)) {
addKeyword(createCaseRule(PsiKeyword.NULL, caseRuleTail, switchBlock));
if (!containedLabels.contains(DEFAULT)) {
if (!containedLabels.contains(PsiKeyword.DEFAULT)) {
addKeyword(createCaseRule(PsiKeyword.NULL + ", " + PsiKeyword.DEFAULT, caseRuleTail, switchBlock));
}
}
@@ -522,7 +524,7 @@ public class JavaKeywordCompletion {
}
@NotNull
private static Set<String> getSwitchCoveredLabels(@Nullable PsiSwitchBlock block) {
private static Set<String> getSwitchCoveredLabels(@Nullable PsiSwitchBlock block, PsiElement position) {
HashSet<String> labels = new HashSet<>();
if (block == null) {
return labels;
@@ -531,10 +533,11 @@ public class JavaKeywordCompletion {
if (body == null) {
return labels;
}
int offset = position.getTextRange().getStartOffset();
for (PsiStatement statement : body.getStatements()) {
if (!(statement instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
if (labelStatement.isDefaultCase()) {
labels.add(DEFAULT);
labels.add(PsiKeyword.DEFAULT);
continue;
}
if (labelStatement.getGuardExpression() != null) {
@@ -547,11 +550,14 @@ public class JavaKeywordCompletion {
for (PsiCaseLabelElement element : list.getElements()) {
if (element instanceof PsiExpression expr &&
ExpressionUtils.isNullLiteral(expr)) {
labels.add(NULL);
labels.add(PsiKeyword.NULL);
continue;
}
if (element instanceof PsiDefaultCaseLabelElement) {
labels.add(DEFAULT);
labels.add(PsiKeyword.DEFAULT);
}
if (element.getTextRange().getStartOffset() >= offset) {
break;
}
if (element instanceof PsiTypeTestPattern typeTestPattern && typeTestPattern.getCheckType() != null) {
PsiType type = typeTestPattern.getCheckType().getType();
@@ -594,7 +600,9 @@ public class JavaKeywordCompletion {
}
}
private static @NotNull LookupElement createCaseRule(@NotNull String caseRuleName, TailType tailType, @Nullable PsiSwitchBlock switchBlock) {
private static @NotNull LookupElement createCaseRule(@NotNull String caseRuleName,
TailType tailType,
@Nullable PsiSwitchBlock switchBlock) {
final String prefix = "case ";
final LookupElement lookupElement = LookupElementBuilder
@@ -643,7 +651,7 @@ public class JavaKeywordCompletion {
.withLookupString(name);
LookupElement withPriority = prioritizeForRule(
new JavaCompletionContributor.IndentingDecorator(TailTypeDecorator.withTail(caseConst, tailType)),
switchBlock);
switchBlock);
myResults.add(withPriority);
}
}
@@ -867,7 +875,8 @@ public class JavaKeywordCompletion {
for (String keyword : keywords) {
if (className == null) {
addKeyword(new OverridableSpace(createKeyword(keyword), TailType.HUMBLE_SPACE_BEFORE_WORD));
} else {
}
else {
addKeyword(createTypeDeclaration(keyword, className));
}
}
@@ -891,9 +900,11 @@ public class JavaKeywordCompletion {
else {
if (nextElement instanceof PsiParameterList l && l.getFirstChild() instanceof PsiJavaToken t) {
nextToken = t.getTokenType();
} else if (nextElement instanceof PsiCodeBlock b && b.getFirstChild() instanceof PsiJavaToken t) {
}
else if (nextElement instanceof PsiCodeBlock b && b.getFirstChild() instanceof PsiJavaToken t) {
nextToken = t.getTokenType();
} else {
}
else {
nextToken = null;
}
}
@@ -1015,8 +1026,9 @@ public class JavaKeywordCompletion {
}
}
};
LookupElement element = new OverridableSpace(LookupElementDecorator.withInsertHandler(createKeyword(PsiKeyword.PERMITS), handler),
TailType.HUMBLE_SPACE_BEFORE_WORD);
LookupElement element =
new OverridableSpace(LookupElementDecorator.withInsertHandler(createKeyword(PsiKeyword.PERMITS), handler),
TailType.HUMBLE_SPACE_BEFORE_WORD);
addKeyword(element);
}
}
@@ -1063,8 +1075,8 @@ public class JavaKeywordCompletion {
}
if (position instanceof PsiIdentifier && position.getParent() instanceof PsiLocalVariable) {
PsiType type = ((PsiLocalVariable) position.getParent()).getType();
if (type instanceof PsiClassType && ((PsiClassType) type).resolve() == null) {
PsiType type = ((PsiLocalVariable)position.getParent()).getType();
if (type instanceof PsiClassType && ((PsiClassType)type).resolve() == null) {
PsiElement grandParent = position.getParent().getParent();
return !(grandParent instanceof PsiDeclarationStatement) || !(grandParent.getParent() instanceof PsiForStatement) ||
((PsiForStatement)grandParent.getParent()).getInitialization() != grandParent;
@@ -1076,7 +1088,8 @@ public class JavaKeywordCompletion {
public static boolean isSuitableForClass(PsiElement position) {
if (psiElement().afterLeaf("@").accepts(position) ||
PsiTreeUtil.getNonStrictParentOfType(position, PsiLiteralExpression.class, PsiComment.class, PsiExpressionCodeFragment.class) != null) {
PsiTreeUtil.getNonStrictParentOfType(position, PsiLiteralExpression.class, PsiComment.class, PsiExpressionCodeFragment.class) !=
null) {
return false;
}
@@ -1210,7 +1223,8 @@ public class JavaKeywordCompletion {
}
private void addBreakContinue() {
PsiLoopStatement loop = PsiTreeUtil.getParentOfType(myPosition, PsiLoopStatement.class, true, PsiLambdaExpression.class, PsiMember.class);
PsiLoopStatement loop =
PsiTreeUtil.getParentOfType(myPosition, PsiLoopStatement.class, true, PsiLambdaExpression.class, PsiMember.class);
LookupElement br = createKeyword(PsiKeyword.BREAK);
LookupElement cont = createKeyword(PsiKeyword.CONTINUE);
@@ -1238,16 +1252,17 @@ public class JavaKeywordCompletion {
addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.YIELD), TailType.INSERT_SPACE));
}
for (PsiLabeledStatement labeled : psiApi().parents(myPosition).takeWhile(notInstanceOf(PsiMember.class)).filter(PsiLabeledStatement.class)) {
for (PsiLabeledStatement labeled : psiApi().parents(myPosition).takeWhile(notInstanceOf(PsiMember.class))
.filter(PsiLabeledStatement.class)) {
addKeyword(TailTypeDecorator.withTail(LookupElementBuilder.create("break " + labeled.getName()).bold(), TailType.SEMICOLON));
}
}
private static boolean isStatementPosition(PsiElement position) {
if (psiElement()
.withSuperParent(2, PsiConditionalExpression.class)
.andNot(psiElement().insideStarting(psiElement(PsiConditionalExpression.class)))
.accepts(position)) {
.withSuperParent(2, PsiConditionalExpression.class)
.andNot(psiElement().insideStarting(psiElement(PsiConditionalExpression.class)))
.accepts(position)) {
return false;
}
@@ -1335,10 +1350,11 @@ public class JavaKeywordCompletion {
}
private void addModuleKeywords() {
PsiElement context = PsiTreeUtil.skipParentsOfType(myPosition.getParent(), PsiErrorElement.class, PsiJavaCodeReferenceElement.class, PsiTypeElement.class);
PsiElement context =
PsiTreeUtil.skipParentsOfType(myPosition.getParent(), PsiErrorElement.class, PsiJavaCodeReferenceElement.class, PsiTypeElement.class);
PsiElement prevElement = PsiTreeUtil.skipWhitespacesAndCommentsBackward(myPosition.getParent());
if (context instanceof PsiJavaFile && !(prevElement instanceof PsiJavaModule) || context instanceof PsiImportList) {
if (context instanceof PsiJavaFile && !(prevElement instanceof PsiJavaModule) || context instanceof PsiImportList) {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.MODULE), TailType.HUMBLE_SPACE_BEFORE_WORD));
if (myPrevLeaf == null || !myPrevLeaf.textMatches(PsiKeyword.OPEN)) {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.OPEN), TailType.HUMBLE_SPACE_BEFORE_WORD));

View File

@@ -0,0 +1,31 @@
public class Main {
sealed interface I {
}
record R(String string) implements I {
}
record R2(String string) implements I {
}
public static void main(String[] args) {
test(new R("1"));
}
public static void test(I i) {
switch (i) {
case R(String s) -> {
System.out.println(1 + s);
}
ca<caret>
case R r -> {
System.out.println(2);
}
default -> {
System.out.println(3);
}
}
}
}

View File

@@ -7,9 +7,10 @@ class Main {
final class C implements I{}
}
void f(I o) {
int casecase = 1;
switch (o) {
case I.C C: System.out.println();
<caret>
cas<caret>
}
}
}

View File

@@ -7,9 +7,10 @@ class Main {
final class C implements I{}
}
void f(I o) {
int casecase = 1;
switch (o) {
case I.C C -> System.out.println();
<caret>
ca<caret>
}
}
}

View File

@@ -12,14 +12,15 @@ import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor;
import com.intellij.ide.ui.UISettings;
import com.intellij.internal.DumpLookupElementWeights;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.NeedsIndex;
import com.intellij.ui.JBColor;
import com.intellij.util.containers.ContainerUtil;
import junit.framework.TestCase;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import java.util.ArrayList;
import java.util.Arrays;
@@ -995,5 +996,23 @@ public class NormalCompletionOrderingTest extends CompletionSortingTestCase {
assertEquals(myFixture.getLookupElementStrings(), List.of("methodB", "methodA"));
}
@NeedsIndex.Full
public void testCompletionRuleCaseOrdering() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_21, () -> {
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.completeBasic();
assertPreferredItems(0, "case null", "case", "case null, default", "case A", "case B");
});
}
@NeedsIndex.Full
public void testCompletionCaseOrdering() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_21, () -> {
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.completeBasic();
assertPreferredItems(0, "case A", "case", "case B", "casecase", "case null", "case null, default");
});
}
private static final String BASE_PATH = "/codeInsight/completion/normalSorting";
}

View File

@@ -12,8 +12,6 @@ import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class NormalSwitchCompletionVariantsTest extends LightFixtureCompletionTestCase {
private static final String[] COMMON_VARIANTS = {"case", "default"};
@@ -91,45 +89,6 @@ public class NormalSwitchCompletionVariantsTest extends LightFixtureCompletionTe
});
}
@NeedsIndex.Full
public void testCompletionRuleCaseOrdering() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_21, () -> {
List<String> lookup = doTestAndGetLookup();
Set<Integer> indexes = Set.of(
lookup.indexOf("case A"),
lookup.indexOf("case B"),
lookup.indexOf("default"),
lookup.indexOf("case null"),
lookup.indexOf("case null, default"));
assertFalse(indexes.contains(-1));
int last = lookup.indexOf("o");
for (Integer index : indexes) {
if (last < index) {
fail("broken order");
}
}
});
}
@NeedsIndex.Full
public void testCompletionCaseOrdering() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_21, () -> {
List<String> lookup = doTestAndGetLookup();
Set<Integer> indexes = Set.of(
lookup.indexOf("case A"),
lookup.indexOf("case B"),
lookup.indexOf("default"),
lookup.indexOf("case null"),
lookup.indexOf("case null, default"));
assertFalse(indexes.contains(-1));
int last = lookup.indexOf("o");
Optional<Integer> afterO = indexes.stream().filter(index -> last < index).findAny();
if (afterO.isEmpty()) {
fail("broken order");
}
});
}
@NeedsIndex.Full
public void testCompletionSealedHierarchyStmt() {
doTest(ArrayUtil.mergeArrays(COMMON_OBJECT_VARIANTS, "case Variant1", "case Variant2"));
@@ -139,6 +98,10 @@ public class NormalSwitchCompletionVariantsTest extends LightFixtureCompletionTe
public void testCompletionSealedHierarchyExpr() {
doTest(ArrayUtil.mergeArrays(COMMON_OBJECT_VARIANTS, "case Variant1", "case Variant2"));
}
@NeedsIndex.Full
public void testCompletionSealedHierarchyExprBeforeCase() {
doTest(new String[]{"case null", "case R", "case R2"});
}
private void doTest(String[] variants) {
final List<String> lookupElementStrings =doTestAndGetLookup();