From 4311bcc92919aaf0f999de39ea015bfe17ffec95 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Fri, 23 Mar 2012 18:36:17 +0400 Subject: [PATCH] #KT-1629 fixed Don't import jet.runtime.SharedVar.Int when typing "val a : Int = ..." Completion testing framework extended with ability to assert tail text. --- .../completion/JetCompletionContributor.java | 181 +++++++++++------- .../basic/OnlyScopedClassesWithoutExplicit.kt | 10 + .../completion/ExpectedCompletionUtils.java | 116 ++++++++++- .../completion/JetBasicCompletionTest.java | 4 + .../JetCompletionMultiTestBase.java | 37 +--- .../jet/completion/JetCompletionTestBase.java | 8 +- 6 files changed, 240 insertions(+), 116 deletions(-) create mode 100644 idea/testData/completion/basic/OnlyScopedClassesWithoutExplicit.kt diff --git a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java index 5f57ecc2ecc..c76c78dc49a 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java @@ -30,8 +30,7 @@ import com.intellij.util.Consumer; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lexer.JetTokens; @@ -41,22 +40,27 @@ import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade; import org.jetbrains.jet.plugin.references.JetSimpleNameReference; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; /** * @author Nikolay Krasko */ public class JetCompletionContributor extends CompletionContributor { + private static class CompletionSession { + public boolean isSomethingAdded = false; + public int customInvocationCount = 0; + } + public JetCompletionContributor() { extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new CompletionProvider() { @Override protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { + result.restartCompletionWhenNothingMatches(); - Set positions = new HashSet(); + CompletionSession session = new CompletionSession(); + session.customInvocationCount = parameters.getInvocationCount(); PsiElement position = parameters.getPosition(); if (!(position.getContainingFile() instanceof JetFile)) { @@ -65,47 +69,72 @@ public class JetCompletionContributor extends CompletionContributor { JetSimpleNameReference jetReference = getJetReference(parameters); if (jetReference != null) { + completeForReference(parameters, result, position, jetReference, session); - // Prevent from adding reference variants from standard reference contributor - result.stopHere(); - - if (isOnlyKeywordCompletion(position)) { - return; + if (!session.isSomethingAdded && session.customInvocationCount == 0) { + // Rerun completion if nothing was found + session.customInvocationCount = 1; + completeForReference(parameters, result, position, jetReference, session); } + } + } + }); + } - if (shouldRunTypeCompletionOnly(position, jetReference)) { - addClasses(parameters, result); - return; - } + private static void completeForReference( + @NotNull CompletionParameters parameters, + @NotNull CompletionResultSet result, + @NotNull PsiElement position, + @NotNull JetSimpleNameReference jetReference, + @NotNull CompletionSession session + ) { + // Prevent from adding reference variants from standard reference contributor + result.stopHere(); - for (Object variant : jetReference.getVariants()) { - addReferenceVariant(result, variant, positions); - } + if (isOnlyKeywordCompletion(position)) { + return; + } - String prefix = result.getPrefixMatcher().getPrefix(); + if (shouldRunTypeCompletionOnly(position, jetReference)) { + if (session.customInvocationCount > 0) { + addClasses(parameters, result); + } + else { + for (Object variant : jetReference.getVariants()) { + if (isTypeDeclaration(variant)) { + addReferenceVariant(result, variant, session); + } + } + } - // Try to avoid computing not-imported descriptors for empty prefix - if (prefix.isEmpty()) { - if (parameters.getInvocationCount() < 2) { - return; - } + return; + } - if (PsiTreeUtil.getParentOfType(jetReference.getElement(), JetDotQualifiedExpression.class) == null) { - return; - } - } + for (Object variant : jetReference.getVariants()) { + addReferenceVariant(result, variant, session); + } - if (shouldRunTopLevelCompletion(parameters, prefix)) { - addClasses(parameters, result); - addJetTopLevelFunctions(jetReference.getExpression(), result, position, positions); - } + String prefix = result.getPrefixMatcher().getPrefix(); - if (shouldRunExtensionsCompletion(parameters, prefix)) { - addJetExtensions(jetReference.getExpression(), result, position); - } - } - } - }); + // Try to avoid computing not-imported descriptors for empty prefix + if (prefix.isEmpty()) { + if (session.customInvocationCount < 2) { + return; + } + + if (PsiTreeUtil.getParentOfType(jetReference.getElement(), JetDotQualifiedExpression.class) == null) { + return; + } + } + + if (shouldRunTopLevelCompletion(parameters, session)) { + addClasses(parameters, result); + addJetTopLevelFunctions(jetReference.getExpression(), result, position, session); + } + + if (shouldRunExtensionsCompletion(parameters, prefix, session)) { + addJetExtensions(jetReference.getExpression(), result, position); + } } private static boolean isOnlyKeywordCompletion(PsiElement position) { @@ -135,18 +164,32 @@ public class JetCompletionContributor extends CompletionContributor { private static void addReferenceVariant( @NotNull CompletionResultSet result, @NotNull Object variant, - @NotNull Set positions) { + @NotNull CompletionSession session) { if (variant instanceof LookupElement) { - addCompletionToResult(result, (LookupElement) variant, positions); + addCompletionToResult(result, (LookupElement) variant, session); } else { - addCompletionToResult(result, LookupElementBuilder.create(variant.toString()), positions); + addCompletionToResult(result, LookupElementBuilder.create(variant.toString()), session); } } + public static boolean isTypeDeclaration(@NotNull Object variant) { + if (variant instanceof LookupElement) { + Object object = ((LookupElement)variant).getObject(); + if (object instanceof JetLookupObject) { + DeclarationDescriptor descriptor = ((JetLookupObject) object).getDescriptor(); + return (descriptor instanceof ClassDescriptor) || + (descriptor instanceof NamespaceDescriptor) || + (descriptor instanceof TypeParameterDescriptor); + } + } + + return false; + } + private static void addJetTopLevelFunctions(JetSimpleNameExpression expression, @NotNull CompletionResultSet result, @NotNull PsiElement position, - @NotNull Set positions) { + @NotNull CompletionSession session) { String actualPrefix = result.getPrefixMatcher().getPrefix(); @@ -161,7 +204,7 @@ public class JetCompletionContributor extends CompletionContributor { for (String name : functionNames) { if (name.contains(actualPrefix)) { for (FunctionDescriptor function : namesCache.getTopLevelFunctionDescriptorsByName(name, expression, scope)) { - addCompletionToResult(result, DescriptorLookupConverter.createLookupElement(resolutionContext, function), positions); + addCompletionToResult(result, DescriptorLookupConverter.createLookupElement(resolutionContext, function), session); } } } @@ -194,8 +237,8 @@ public class JetCompletionContributor extends CompletionContributor { return false; } - private static boolean shouldRunTopLevelCompletion(@NotNull CompletionParameters parameters, String prefix) { - if (parameters.getInvocationCount() == 0 && prefix.length() < 3) { + private static boolean shouldRunTopLevelCompletion(@NotNull CompletionParameters parameters, CompletionSession session) { + if (session.customInvocationCount == 0) { return false; } @@ -212,8 +255,8 @@ public class JetCompletionContributor extends CompletionContributor { return false; } - private static boolean shouldRunExtensionsCompletion(CompletionParameters parameters, String prefix) { - if (parameters.getInvocationCount() == 0 && prefix.length() < 3) { + private static boolean shouldRunExtensionsCompletion(CompletionParameters parameters, String prefix, CompletionSession session) { + if (session.customInvocationCount == 0 && prefix.length() < 3) { return false; } @@ -243,37 +286,29 @@ public class JetCompletionContributor extends CompletionContributor { private static void addCompletionToResult( @NotNull CompletionResultSet result, @NotNull LookupElement element, - @NotNull Set positions) { - -// LookupPositionObject lookupPosition = getLookupPosition(element); -// if (lookupPosition != null) { -// if (!positions.contains(lookupPosition)) { -// positions.add(lookupPosition); -// result.addElement(element); -// } -// -// // There is already an element with same position - ignore duplicate -// } -// else { - result.addElement(element); -// } - } + @NotNull CompletionSession session) { - private static LookupPositionObject getLookupPosition(LookupElement element) { - Object lookupObject = element.getObject(); - if (lookupObject instanceof PsiElement) { - return new LookupPositionObject((PsiElement) lookupObject); - } - else if (lookupObject instanceof JetLookupObject) { - PsiElement psiElement = ((JetLookupObject) lookupObject).getPsiElement(); - if (psiElement != null) { - return new LookupPositionObject(psiElement); - } + if (result.getPrefixMatcher().prefixMatches(element)) { + result.addElement(element); + session.isSomethingAdded = true; } - - return null; } + //private static LookupPositionObject getLookupPosition(LookupElement element) { + // Object lookupObject = element.getObject(); + // if (lookupObject instanceof PsiElement) { + // return new LookupPositionObject((PsiElement) lookupObject); + // } + // else if (lookupObject instanceof JetLookupObject) { + // PsiElement psiElement = ((JetLookupObject) lookupObject).getPsiElement(); + // if (psiElement != null) { + // return new LookupPositionObject(psiElement); + // } + // } + // + // return null; + //} + @Override public void beforeCompletion(@NotNull CompletionInitializationContext context) { super.beforeCompletion(context); //To change body of overridden methods use File | Settings | File Templates. diff --git a/idea/testData/completion/basic/OnlyScopedClassesWithoutExplicit.kt b/idea/testData/completion/basic/OnlyScopedClassesWithoutExplicit.kt new file mode 100644 index 00000000000..80dd034f048 --- /dev/null +++ b/idea/testData/completion/basic/OnlyScopedClassesWithoutExplicit.kt @@ -0,0 +1,10 @@ +package first + +fun firstFun() { + val a = In +} + +// RUNTIME: 1 +// TIME: 0 +// EXIST: Int~(jet) +// ABSENT: Int~(jet.runtime.SharedVar) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/completion/ExpectedCompletionUtils.java b/idea/tests/org/jetbrains/jet/completion/ExpectedCompletionUtils.java index 5b0b9b10783..a57bed3eef5 100644 --- a/idea/tests/org/jetbrains/jet/completion/ExpectedCompletionUtils.java +++ b/idea/tests/org/jetbrains/jet/completion/ExpectedCompletionUtils.java @@ -16,8 +16,13 @@ package org.jetbrains.jet.completion; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; +import junit.framework.Assert; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,6 +39,39 @@ import java.util.List; * @author Nikolay Krasko */ public class ExpectedCompletionUtils { + + public static class CompletionProposal { + public static final String TAIL_FLAG = "~"; + + private final String lookupString; + private final String tailString; + + + + public CompletionProposal(@NotNull String lookupString, @Nullable String tailString) { + this.lookupString = lookupString; + this.tailString = tailString != null ? tailString.trim() : null; + } + + public boolean isSuitable(CompletionProposal proposal) { + if (proposal.tailString != null) { + if (!proposal.tailString.equals(tailString)) { + return false; + } + } + + return lookupString.equals(proposal.lookupString); + } + + @Override + public String toString() { + if (tailString != null) { + return lookupString + TAIL_FLAG + tailString; + } + + return lookupString; + } + } public static final String EXIST_LINE_PREFIX = "// EXIST:"; public static final String ABSENT_LINE_PREFIX = "// ABSENT:"; @@ -57,13 +95,30 @@ public class ExpectedCompletionUtils { } @NotNull - public String[] itemsShouldExist(String fileText) { - return findListWithPrefix(existLinePrefix, fileText); + public CompletionProposal[] itemsShouldExist(String fileText) { + return processProposalAssertions(existLinePrefix, fileText); } @NotNull - public String[] itemsShouldAbsent(String fileText) { - return findListWithPrefix(absentLinePrefix, fileText); + public CompletionProposal[] itemsShouldAbsent(String fileText) { + return processProposalAssertions(absentLinePrefix, fileText); + } + + public static CompletionProposal[] processProposalAssertions(String prefix, String fileText) { + List proposals = new ArrayList(); + for (String proposalStr : findListWithPrefix(prefix, fileText)) { + int tailChar = proposalStr.indexOf(CompletionProposal.TAIL_FLAG); + + if (tailChar > 0) { + proposals.add(new CompletionProposal(proposalStr.substring(0, tailChar), + proposalStr.substring(tailChar + 1, proposalStr.length()))); + } + else { + proposals.add(new CompletionProposal(proposalStr, null)); + } + } + + return ArrayUtil.toObjectArray(proposals, CompletionProposal.class); } @Nullable @@ -137,4 +192,57 @@ public class ExpectedCompletionUtils { return result; } + + protected static void assertContainsRenderedItems(CompletionProposal[] expected, LookupElement[] items) { + List itemsInformation = getItemsInformation(items); + + for (CompletionProposal expectedProposal : expected) { + boolean isFound = false; + + for (CompletionProposal proposal : itemsInformation) { + if (proposal.isSuitable(expectedProposal)) { + isFound = true; + break; + } + } + + Assert.assertTrue("Expected '" + expectedProposal + "' not found in " + listToString(itemsInformation), isFound); + } + } + + protected static void assertNotContainsRenderedItems(CompletionProposal[] unexpected,LookupElement[] items) { + List itemsInformation = getItemsInformation(items); + + for (CompletionProposal unexpectedProposal : unexpected) { + for (CompletionProposal proposal : itemsInformation) { + Assert.assertFalse("Unexpected '" + unexpectedProposal + "' presented in " + listToString(itemsInformation), + proposal.isSuitable(unexpectedProposal)); + } + } + } + + protected static List getItemsInformation(LookupElement[] items) { + final LookupElementPresentation presentation = new LookupElementPresentation(); + + List result = new ArrayList(); + if (items != null) { + for (LookupElement item : items) { + item.renderElement(presentation); + result.add(new ExpectedCompletionUtils.CompletionProposal(item.getLookupString(), presentation.getTailText())); + } + } + + return result; + } + + protected static String listToString(List items) { + return StringUtil.join( + Collections2.transform(items, new Function() { + @Override + public String apply(@Nullable CompletionProposal proposal) { + assert proposal != null; + return proposal.toString(); + } + }), "\n"); + } } diff --git a/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java b/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java index 37830db0d69..9793160dbdc 100644 --- a/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java +++ b/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java @@ -97,6 +97,10 @@ public class JetBasicCompletionTest extends JetCompletionTestBase { doTest(); } + public void testOnlyScopedClassesWithoutExplicit() { + doTest(); + } + public void testOverloadFunctions() { doTest(); } diff --git a/idea/tests/org/jetbrains/jet/completion/JetCompletionMultiTestBase.java b/idea/tests/org/jetbrains/jet/completion/JetCompletionMultiTestBase.java index 0d96db3f10b..370bbc30ea1 100644 --- a/idea/tests/org/jetbrains/jet/completion/JetCompletionMultiTestBase.java +++ b/idea/tests/org/jetbrains/jet/completion/JetCompletionMultiTestBase.java @@ -17,10 +17,6 @@ package org.jetbrains.jet.completion; import com.intellij.codeInsight.completion.CompletionTestCase; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.util.containers.HashSet; - -import java.util.Set; /** * @author Nikolay Krasko @@ -41,8 +37,8 @@ public abstract class JetCompletionMultiTestBase extends CompletionTestCase { final String fileText = getFile().getText(); final ExpectedCompletionUtils completionUtils = new ExpectedCompletionUtils(); - assertContainsItems(completionUtils.itemsShouldExist(fileText)); - assertNotContainItems(completionUtils.itemsShouldAbsent(fileText)); + ExpectedCompletionUtils.assertContainsRenderedItems(completionUtils.itemsShouldExist(fileText), myItems); + ExpectedCompletionUtils.assertNotContainsRenderedItems(completionUtils.itemsShouldAbsent(fileText), myItems); Integer itemsNumber = completionUtils.getExpectedNumber(fileText); if (itemsNumber != null) { @@ -56,33 +52,4 @@ public abstract class JetCompletionMultiTestBase extends CompletionTestCase { protected void doFileTest() { doFileTest(1); } - - // Copied from com.intellij.codeInsight.completion.LightCompletionTestCase - protected void assertContainsItems(final String... expected) { - final Set actual = getLookupStrings(); - for (String s : expected) { - assertTrue("Expected '" + s + "' not found in " + actual, - actual.contains(s)); - } - } - - // Copied from com.intellij.codeInsight.completion.LightCompletionTestCase - protected void assertNotContainItems(final String... unexpected) { - final Set actual = getLookupStrings(); - for (String s : unexpected) { - assertFalse("Unexpected '" + s + "' presented in " + actual, - actual.contains(s)); - } - } - - // Copied from com.intellij.codeInsight.completion.LightCompletionTestCase - private Set getLookupStrings() { - final Set actual = new HashSet(); - if (myItems != null) { - for (LookupElement lookupElement : myItems) { - actual.add(lookupElement.getLookupString()); - } - } - return actual; - } } diff --git a/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java b/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java index 0d8af2bd7a9..663c14df8b9 100644 --- a/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java +++ b/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java @@ -59,14 +59,14 @@ public abstract class JetCompletionTestBase extends LightCompletionTestCase { complete(completionTime == null ? 1 : completionTime); - final String[] expected = completionUtils.itemsShouldExist(fileText); - final String[] unexpected = completionUtils.itemsShouldAbsent(fileText); + ExpectedCompletionUtils.CompletionProposal[] expected = completionUtils.itemsShouldExist(fileText); + ExpectedCompletionUtils.CompletionProposal[] unexpected = completionUtils.itemsShouldAbsent(fileText); Integer itemsNumber = completionUtils.getExpectedNumber(fileText); assertTrue("Should be some assertions about completion", expected.length != 0 || unexpected.length != 0 || itemsNumber != null); - assertContainsItems(expected); - assertNotContainItems(unexpected); + ExpectedCompletionUtils.assertContainsRenderedItems(expected, myItems); + ExpectedCompletionUtils.assertNotContainsRenderedItems(unexpected, myItems); if (itemsNumber != null) { assertEquals("Invalid number of completion items", itemsNumber.intValue(), myItems.length); -- GitLab