Code completion updates for enhanced switches (IDEA-202340)

- Support inside switch expression
- Support rule-based labels
- Support break inside expression
- Suggest 'switch' keyword in expressions
- Insert ':' or '->' depending on the switch type
This commit is contained in:
Tagir Valeev
2018-12-08 16:27:58 +07:00
parent 86e1009135
commit 6ea7ea44c2
20 changed files with 208 additions and 36 deletions

View File

@@ -570,13 +570,13 @@ public class ExpectedTypesProvider {
getExpectedArgumentsTypesForNewExpression((PsiNewExpression)parent.getParent(), list);
}
else if (parent instanceof PsiSwitchLabelStatementBase) {
PsiSwitchStatement switchStatement = ((PsiSwitchLabelStatementBase)parent).getEnclosingSwitchStatement();
if (switchStatement != null) {
PsiExpression expression = switchStatement.getExpression();
PsiSwitchBlock switchBlock = ((PsiSwitchLabelStatementBase)parent).getEnclosingSwitchBlock();
if (switchBlock != null) {
PsiExpression expression = switchBlock.getExpression();
if (expression != null) {
PsiType type = expression.getType();
if (type != null) {
myResult.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.CASE_COLON));
myResult.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailTypes.forSwitchLabel(switchBlock)));
}
}
}

View File

@@ -15,11 +15,17 @@
*/
package com.intellij.codeInsight;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.simple.BracesTailType;
import com.intellij.codeInsight.completion.simple.ParenthesesTailType;
import com.intellij.codeInsight.completion.simple.RParenthTailType;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiSwitchBlock;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.siyeh.ig.psiutils.SwitchUtils;
import org.jetbrains.annotations.NotNull;
public class TailTypes {
public static final TailType CALL_RPARENTH = new RParenthTailType(){
@@ -124,11 +130,38 @@ public class TailTypes {
return styleSettings.SPACE_WITHIN_IF_PARENTHESES;
}
};
private static final String ARROW = " -> ";
public static final TailType CASE_ARROW = new TailType() {
@Override
public int processTail(Editor editor, int tailOffset) {
Document document = editor.getDocument();
document.insertString(tailOffset, ARROW);
return moveCaret(editor, tailOffset, ARROW.length());
}
@Override
public boolean isApplicable(@NotNull InsertionContext context) {
Document document = context.getDocument();
int offset = context.getTailOffset();
int length = document.getTextLength();
int endOffset = offset + ARROW.length();
if (endOffset > length) return true;
return !document.getText(new TextRange(offset, endOffset)).equals(ARROW);
}
@Override
public String toString() {
return "CASE_ARROW";
}
};
private static final TailType BRACES = new BracesTailType();
public static final TailType FINALLY_LBRACE = BRACES;
public static final TailType TRY_LBRACE = BRACES;
public static final TailType DO_LBRACE = BRACES;
public static TailType forSwitchLabel(@NotNull PsiSwitchBlock block) {
return SwitchUtils.isRuleFormatSwitch(block) ? CASE_ARROW : TailType.CASE_COLON;
}
private TailTypes() {}

View File

@@ -4,6 +4,7 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.TailTypes;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
import com.intellij.codeInsight.lookup.*;
@@ -81,13 +82,13 @@ public class JavaCompletionContributor extends CompletionContributor {
psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
public static final ElementPattern<PsiElement> IN_SWITCH_LABEL =
psiElement().withSuperParent(2, psiElement(PsiExpressionList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2, PsiSwitchStatement.class)));
psiElement().withSuperParent(2, psiElement(PsiExpressionList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2, PsiSwitchBlock.class)));
private static final ElementPattern IN_ENUM_SWITCH_LABEL =
psiElement().withSuperParent(2, psiElement(PsiExpressionList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2,
psiElement(PsiSwitchStatement.class).with(new PatternCondition<PsiSwitchStatement>("enumExpressionType") {
psiElement(PsiSwitchBlock.class).with(new PatternCondition<PsiSwitchBlock>("enumExpressionType") {
@Override
public boolean accepts(@NotNull PsiSwitchStatement psiSwitchStatement, ProcessingContext context) {
PsiExpression expression = psiSwitchStatement.getExpression();
public boolean accepts(@NotNull PsiSwitchBlock psiSwitchBlock, ProcessingContext context) {
PsiExpression expression = psiSwitchBlock.getExpression();
if (expression == null) return false;
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
return aClass != null && aClass.isEnum();
@@ -426,7 +427,13 @@ public class JavaCompletionContributor extends CompletionContributor {
MultiMap<CompletionResultSet, LookupElement> items = MultiMap.create();
final PsiElement position = parameters.getPosition();
final boolean first = parameters.getInvocationCount() <= 1;
final boolean isSwitchLabel = IN_ENUM_SWITCH_LABEL.accepts(position);
final TailType switchLabelTail;
if (IN_ENUM_SWITCH_LABEL.accepts(position)) {
PsiSwitchBlock block = Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class));
switchLabelTail = TailTypes.forSwitchLabel(block);
} else {
switchLabelTail = null;
}
final boolean isAfterNew = JavaClassNameCompletionContributor.AFTER_NEW.accepts(position);
final boolean pkgContext = JavaCompletionUtil.inSomePackage(position);
final PsiType[] expectedTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true);
@@ -453,8 +460,8 @@ public class JavaCompletionContributor extends CompletionContributor {
continue;
}
if (isSwitchLabel) {
items.putValue(result1, new IndentingDecorator(TailTypeDecorator.withTail(element, TailType.createSimpleTailType(':'))));
if (switchLabelTail != null) {
items.putValue(result1, new IndentingDecorator(TailTypeDecorator.withTail(element, switchLabelTail)));
}
else {
final LookupItem item = element.as(LookupItem.CLASS_CONDITION_KEY);

View File

@@ -318,45 +318,46 @@ public class JavaKeywordCompletion {
}
private void addCaseDefault() {
if (getSwitchFromLabelPosition(myPosition) != null) {
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
if (switchBlock != null) {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CASE), TailType.INSERT_SPACE));
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.CASE_COLON));
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailTypes.forSwitchLabel(switchBlock)));
}
}
private static PsiSwitchStatement getSwitchFromLabelPosition(PsiElement position) {
private static PsiSwitchBlock getSwitchFromLabelPosition(PsiElement position) {
PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiStatement.class, false, PsiMember.class);
if (statement == null || statement.getTextRange().getStartOffset() != position.getTextRange().getStartOffset()) {
return null;
}
if (!(statement instanceof PsiSwitchLabelStatement) && statement.getParent() instanceof PsiCodeBlock) {
return ObjectUtils.tryCast(statement.getParent().getParent(), PsiSwitchStatement.class);
if (!(statement instanceof PsiSwitchLabelStatementBase) && statement.getParent() instanceof PsiCodeBlock) {
return ObjectUtils.tryCast(statement.getParent().getParent(), PsiSwitchBlock.class);
}
return null;
}
void addEnumCases() {
PsiSwitchStatement switchStatement = getSwitchFromLabelPosition(myPosition);
PsiExpression expression = switchStatement == null ? null : switchStatement.getExpression();
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
PsiExpression expression = switchBlock == null ? null : switchBlock.getExpression();
PsiClass switchType = expression == null ? null : PsiUtil.resolveClassInClassTypeOnly(expression.getType());
if (switchType == null || !switchType.isEnum()) return;
Set<PsiField> used = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(switchStatement);
Set<PsiField> used = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(switchBlock);
TailType tailType = TailTypes.forSwitchLabel(switchBlock);
for (PsiField field : switchType.getAllFields()) {
String name = field.getName();
if (!(field instanceof PsiEnumConstant) || used.contains(CompletionUtil.getOriginalOrSelf(field)) || name == null) {
continue;
}
String prefix = "case ";
String suffix = name + ":";
LookupElementBuilder caseConst = LookupElementBuilder
.create(field, prefix + suffix)
.create(field, prefix + name)
.bold()
.withPresentableText(prefix)
.withTailText(suffix)
.withTailText(name)
.withLookupString(name);
myResults.add(new JavaCompletionContributor.IndentingDecorator(caseConst));
myResults.add(new JavaCompletionContributor.IndentingDecorator(TailTypeDecorator.withTail(caseConst, tailType)));
}
}
@@ -430,6 +431,9 @@ public class JavaKeywordCompletion {
if (PsiTreeUtil.getParentOfType(myPosition, PsiAnnotation.class) == null) {
if (!statementPosition) {
addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.NEW), TailType.INSERT_SPACE));
if (PsiUtil.getLanguageLevel(myPosition.getContainingFile()).isAtLeast(LanguageLevel.JDK_12_PREVIEW)) {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.SWITCH), TailTypes.SWITCH_LPARENTH));
}
}
addKeyword(createKeyword(PsiKeyword.NULL));
}
@@ -762,6 +766,8 @@ public class JavaKeywordCompletion {
}
if (psiElement().inside(PsiSwitchStatement.class).accepts(myPosition)) {
addKeyword(br);
} else if (psiElement().inside(PsiSwitchExpression.class).accepts(myPosition)) {
addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.BREAK), TailType.INSERT_SPACE));
}
for (PsiLabeledStatement labeled : psiApi().parents(myPosition).takeWhile(notInstanceOf(PsiMember.class)).filter(PsiLabeledStatement.class)) {

View File

@@ -29,6 +29,7 @@ import com.intellij.util.Consumer;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.SwitchUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -170,24 +171,20 @@ public class ReferenceExpressionCompletionContributor {
@NotNull
public static Set<PsiField> findConstantsUsedInSwitch(@Nullable PsiElement position) {
return JavaCompletionContributor.IN_SWITCH_LABEL.accepts(position)
? findConstantsUsedInSwitch(ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(position, PsiSwitchStatement.class)))
? findConstantsUsedInSwitch(ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)))
: Collections.emptySet();
}
@NotNull
public static Set<PsiField> findConstantsUsedInSwitch(@NotNull PsiSwitchStatement sw) {
public static Set<PsiField> findConstantsUsedInSwitch(@NotNull PsiSwitchBlock sw) {
final PsiCodeBlock body = sw.getBody();
if (body == null) return Collections.emptySet();
Set<PsiField> used = ContainerUtil.newLinkedHashSet();
for (PsiStatement statement : body.getStatements()) {
if (statement instanceof PsiSwitchLabelStatement) {
final PsiExpression value = ((PsiSwitchLabelStatement)statement).getCaseValue();
if (value instanceof PsiReferenceExpression) {
final PsiElement target = ((PsiReferenceExpression)value).resolve();
if (target instanceof PsiField) {
used.add(CompletionUtil.getOriginalOrSelf((PsiField)target));
}
if (statement instanceof PsiSwitchLabelStatementBase) {
for (PsiEnumConstant constant : SwitchUtils.findEnumConstants((PsiSwitchLabelStatementBase)statement)) {
used.add(CompletionUtil.getOriginalOrSelf(constant));
}
}
}

View File

@@ -15,7 +15,7 @@
*/
package com.intellij.psi.filters.getters;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.TailTypes;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.TailTypeDecorator;
@@ -26,6 +26,7 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -69,7 +70,9 @@ public class JavaMembersGetter extends MembersGetter {
}
private void addConstantsFromReferencedClassesInSwitch(final Consumer<? super LookupElement> results) {
final Set<PsiField> fields = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(myPlace);
if (!JavaCompletionContributor.IN_SWITCH_LABEL.accepts(myPlace)) return;
PsiSwitchBlock block = ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(myPlace, PsiSwitchBlock.class));
final Set<PsiField> fields = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(block);
final Set<PsiClass> classes = new HashSet<>();
for (PsiField field : fields) {
ContainerUtil.addIfNotNull(classes, field.getContainingClass());
@@ -78,7 +81,7 @@ public class JavaMembersGetter extends MembersGetter {
processMembers(element -> {
//noinspection SuspiciousMethodCalls
if (!fields.contains(element.getObject())) {
results.consume(TailTypeDecorator.withTail(element, TailType.CASE_COLON));
results.consume(TailTypeDecorator.withTail(element, TailTypes.forSwitchLabel(block)));
}
}, aClass, true, false);
}

View File

@@ -0,0 +1,8 @@
public class ConstConfig {
int test(int x) {
return switch (x) {
case 1:
if (Math.random() > 0.5) br<caret>
};
}
}

View File

@@ -0,0 +1,8 @@
public class ConstConfig {
int test(int x) {
return switch (x) {
case 1:
if (Math.random() > 0.5) break <caret>
};
}
}

View File

@@ -0,0 +1,9 @@
public class ConstConfig {
{
switch (a) {
case Foooo -> {}
def<caret>
}
}
}

View File

@@ -0,0 +1,9 @@
public class ConstConfig {
{
switch (a) {
case Foooo -> {}
default -> <caret>
}
}
}

View File

@@ -0,0 +1,11 @@
public class ConstConfig {
enum X {FOO, BAR, BAZ}
void test(X x) {
switch (x) {
case BAR -> {}
case BAZ -> {}
case FO<caret>
}
}
}

View File

@@ -0,0 +1,11 @@
public class ConstConfig {
enum X {FOO, BAR, BAZ}
void test(X x) {
switch (x) {
case BAR -> {}
case BAZ -> {}
case FOO -> <caret>
}
}
}

View File

@@ -0,0 +1,9 @@
public class ConstConfig {
enum X {FOO, BAR, BAZ}
void test(X x) {
switch (x) {
case BAR, FO<caret>: {}
}
}
}

View File

@@ -0,0 +1,9 @@
public class ConstConfig {
enum X {FOO, BAR, BAZ}
void test(X x) {
switch (x) {
case BAR, FOO:<caret> {}
}
}
}

View File

@@ -0,0 +1,9 @@
public class ConstConfig {
enum X {FOO, BAR, BAZ}
void test(X x) {
switch (x) {
case BAR, FO<caret> -> {}
}
}
}

View File

@@ -0,0 +1,9 @@
public class ConstConfig {
enum X {FOO, BAR, BAZ}
void test(X x) {
switch (x) {
case BAR, FOO<caret> -> {}
}
}
}

View File

@@ -0,0 +1,5 @@
public class ConstConfig {
int test(int x) {
return sw<caret>
}
}

View File

@@ -0,0 +1,5 @@
public class ConstConfig {
int test(int x) {
return switch (<caret>)
}
}

View File

@@ -0,0 +1,24 @@
// 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.completion
import com.intellij.testFramework.LightProjectDescriptor
import groovy.transform.CompileStatic
import org.jetbrains.annotations.NotNull
@CompileStatic
class Normal12CompletionTest extends NormalCompletionTestCase {
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_12
}
void testDefaultInRuleSwitch() throws Throwable { doTest() }
void testLabelInRuleSwitch() throws Throwable { doTest() }
void testSecondLabelInOldSwitch() throws Throwable { doTest() }
void testSecondLabelInRuleSwitch() throws Throwable { doTest() }
void testSwitchExpressionStart() throws Throwable { doTest() }
void testBreakInSwitchExpression() throws Throwable { doTest() }
}

View File

@@ -142,7 +142,7 @@ public class SwitchUtils {
* @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.
*/
public static boolean isRuleFormatSwitch(PsiSwitchBlock block) {
public static boolean isRuleFormatSwitch(@NotNull PsiSwitchBlock block) {
if (PsiUtil.getLanguageLevel(block).isLessThan(LanguageLevel.JDK_12_PREVIEW)) {
return false;
}