From 5a3156abc4305baa23f1d84798bcca8655ae7c18 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 15 Sep 2020 18:06:17 +0700 Subject: [PATCH] [java-completion] IDEA-212637 String.toUpperCase/toLowerCase: suggest using Locale parameter in code completion MethodMergingMinimalTail is updated, as now additional completion option is available GitOrigin-RevId: 290bdaf0e7aea3c2b676de052e118b7d08d0c525 --- .../completion/ArgumentSuggester.java | 88 +++++++++++++++++++ .../completion/JavaCompletionContributor.java | 1 + .../normal/MethodMergingMinimalTail.java | 4 +- .../MethodMergingMinimalTail_after.java | 4 +- .../completion/Normal8CompletionTest.groovy | 19 ++++ 5 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/ArgumentSuggester.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ArgumentSuggester.java b/java/java-impl/src/com/intellij/codeInsight/completion/ArgumentSuggester.java new file mode 100644 index 000000000000..af93587b6e1f --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ArgumentSuggester.java @@ -0,0 +1,88 @@ +// 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. +package com.intellij.codeInsight.completion; + +import com.intellij.codeInsight.ExpectedTypeInfo; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.intellij.codeInsight.lookup.TypedLookupItem; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiMethodCallExpression; +import com.intellij.psi.PsiReferenceExpression; +import com.intellij.psi.PsiType; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.PlatformIcons; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.TypeUtils; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class ArgumentSuggester { + static @NotNull Collection suggestArgument(PsiReferenceExpression ref, List expectedTypeInfos) { + PsiExpression qualifier = ref.getQualifierExpression(); + + if (qualifier == null) return Collections.emptyList(); + PsiType type = qualifier.getType(); + if (TypeUtils.isJavaLangString(type)) { + if (PsiUtil.isLanguageLevel7OrHigher(ref)) { + return StreamEx.of(new MethodWithArgument("toLowerCase", type, "java.util.Locale.ROOT", "Locale.ROOT"), + new MethodWithArgument("toUpperCase", type, "java.util.Locale.ROOT", "Locale.ROOT"), + new MethodWithArgument("getBytes", PsiType.BYTE.createArrayType(), "java.nio.charset.StandardCharsets.UTF_8", "StandardCharsets.UTF_8")) + .filter(expectedTypeInfos.isEmpty() ? + element -> true : + element -> ContainerUtil.exists(expectedTypeInfos, ti -> ti.getType().equals(element.getType()))) + .map(element -> PrioritizedLookupElement.withPriority(element, 1)).toList(); + } + } + return Collections.emptyList(); + } + + private static class MethodWithArgument extends LookupElement implements TypedLookupItem { + private final String myMethod; + private final PsiType myType; + private final String myArgument; + private final String myPresentation; + + MethodWithArgument(String method, PsiType type, String argument, String presentation) { + myMethod = method; + myType = type; + myArgument = argument; + myPresentation = presentation; + } + + @Override + public @Nullable PsiType getType() { + return myType; + } + + @Override + public @NotNull String getLookupString() { + return myMethod + "(" + myPresentation + ")"; + } + + @Override + public void renderElement(LookupElementPresentation presentation) { + super.renderElement(presentation); + presentation.setTypeText(myType.getPresentableText()); + presentation.setIcon(PlatformIcons.METHOD_ICON); + } + + @Override + public void handleInsert(@NotNull InsertionContext context) { + String insertString = myMethod + "(" + myArgument + ")"; + context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), insertString); + context.commitDocument(); + PsiMethodCallExpression call = + PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiMethodCallExpression.class, false); + if (call == null) return; + + JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(call.getArgumentList()); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index 1504bfcf41f4..daf66bb1653c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -502,6 +502,7 @@ public class JavaCompletionContributor extends CompletionContributor implements if (!smart) { items.addAll(StreamConversion.addToStreamConversion((PsiReferenceExpression)parent, parameters)); } + items.addAll(ArgumentSuggester.suggestArgument((PsiReferenceExpression)parent, smart ? expected : Collections.emptyList())); } if (IMPORT_REFERENCE.accepts(position)) { diff --git a/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail.java b/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail.java index aa5945895222..e0e1d5dea85d 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail.java +++ b/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail.java @@ -1,7 +1,7 @@ class Foo { { - foo("someTestAttachment", "".getByt) + foo("someTestAttachment", "".star) } - void foo(String s, byte[] z) {} + void foo(String s, boolean z) {} } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail_after.java b/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail_after.java index a89e601edec6..5132c4e3d34e 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail_after.java +++ b/java/java-tests/testData/codeInsight/completion/normal/MethodMergingMinimalTail_after.java @@ -1,7 +1,7 @@ class Foo { { - foo("someTestAttachment", "".getBytes()) + foo("someTestAttachment", "".startsWith()) } - void foo(String s, byte[] z) {} + void foo(String s, boolean z) {} } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy index 4e6228d12958..b5195fefbebd 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy @@ -488,4 +488,23 @@ class Test88 { void testNoStreamSuggestionsInMethodReference() { doAntiTest() } + @NeedsIndex.ForStandardLibrary + void testToLowerCase() { + myFixture.configureByText 'a.java', 'class C { String s = "hello".toUp }' + myFixture.completeBasic() + assert myFixture.lookupElementStrings == ['toUpperCase(Locale.ROOT)', 'toUpperCase', 'toUpperCase'] + myFixture.type('\n') + myFixture.checkResult('import java.util.Locale;\n\n' + + 'class C { String s = "hello".toUpperCase(Locale.ROOT) }') + } + + @NeedsIndex.ForStandardLibrary + void testGetBytes() { + myFixture.configureByText 'a.java', 'class C { byte[] s = "hello".getB }' + myFixture.completeBasic() + assert myFixture.lookupElementStrings == ['getBytes(StandardCharsets.UTF_8)', 'getBytes', 'getBytes', 'getBytes', 'getBytes'] + myFixture.type('\n') + myFixture.checkResult('class C { byte[] s = "hello".getBytes(java.nio.charset.StandardCharsets.UTF_8) }') + } + } \ No newline at end of file