diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 81096781173..876ffb80654 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -27,6 +27,7 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.ProcessingContext +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassifierDescriptor @@ -39,12 +40,10 @@ import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.isAncestor -import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull class BasicCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, @@ -79,7 +78,9 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, NAMED_ARGUMENTS_ONLY(descriptorKindFilter = null, classKindFilter = null), - PARAMETER_NAME(descriptorKindFilter = null, classKindFilter = null) + PARAMETER_NAME(descriptorKindFilter = null, classKindFilter = null), + + SUPER_QUALIFIER(descriptorKindFilter = DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS, classKindFilter = null) } private val completionKind = calcCompletionKind() @@ -136,6 +137,10 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, // expression we are at first of it val typeReference = position.getStrictParentOfType() if (typeReference != null) { + if (typeReference.parent is JetSuperExpression) { + return CompletionKind.SUPER_QUALIFIER + } + val firstPartReference = PsiTreeUtil.findChildOfType(typeReference, javaClass()) if (firstPartReference == nameExpression) { return CompletionKind.TYPES @@ -182,6 +187,11 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } } + if (completionKind == CompletionKind.SUPER_QUALIFIER) { + completeSuperQualifier() + return + } + // if we are typing parameter name, restart completion each time we type an upper case letter because new suggestions will appear (previous words can be used as user prefix) if (parameterNameAndTypeCompletion != null) { val prefixPattern = StandardPatterns.string().with(object : PatternCondition("Prefix ends with uppercase letter") { @@ -306,6 +316,28 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, parameterNameAndTypeCompletion?.addFromAllClasses(parameters, indicesHelper) } + private fun completeSuperQualifier() { + val classOrObject = position.parents.firstIsInstanceOrNull() ?: return + val classDescriptor = resolutionFacade.resolveToDescriptor(classOrObject) as ClassDescriptor + var superClasses = classDescriptor.defaultType.constructor.supertypes + .map { it.constructor.declarationDescriptor as? ClassDescriptor } + .filterNotNull() + + //TODO: IMO it's not good that Any is to be added manually + if (superClasses.all { it.kind == ClassKind.INTERFACE }) { + superClasses += KotlinBuiltIns.getInstance().any + } + + if (!isNoQualifierContext()) { + val referenceVariantsSet = referenceVariants.toSet() + superClasses = superClasses.filter { it in referenceVariantsSet } + } + + superClasses + .map { lookupElementFactory.createLookupElement(it, boldImmediateMembers = false, qualifyNestedClasses = true, includeClassTypeArguments = false) } + .forEach { collector.addElement(it) } + } + override fun createSorter(): CompletionSorter { var sorter = super.createSorter() diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index 27b54172205..8361e34e38e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -74,6 +74,8 @@ public class KotlinCompletionContributor : CompletionContributor() { isInClassHeader(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER // do not add '$' to not interrupt class declaration parsing + isInUnclosedSuperQualifier(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">" + else -> specialLambdaSignatureDummyIdentifier(tokenBefore) ?: specialExtensionReceiverDummyIdentifier(tokenBefore) ?: specialInTypeArgsDummyIdentifier(tokenBefore) @@ -415,4 +417,14 @@ public class KotlinCompletionContributor : CompletionContributor() { } return balance } + + private fun isInUnclosedSuperQualifier(tokenBefore: PsiElement?): Boolean { + if (tokenBefore == null) return false + val tokensToSkip = TokenSet.orSet(TokenSet.create(JetTokens.IDENTIFIER, JetTokens.DOT ), JetTokens.WHITE_SPACE_OR_COMMENT_BIT_SET) + val tokens = sequence(tokenBefore) { it.prevLeaf() } + val ltToken = tokens.firstOrNull { it.node.elementType !in tokensToSkip } ?: return false + if (ltToken.node.elementType != JetTokens.LT) return false + val superToken = ltToken.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment } + return superToken?.node?.elementType == JetTokens.SUPER_KEYWORD + } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt index e07727ef82d..96b08841591 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt @@ -146,7 +146,9 @@ public class LookupElementFactory( if (nestLevel > 0) { var itemText = psiClass.getName() for (i in 1..nestLevel) { - itemText = containerName.substringAfterLast('.') + "." + itemText + val outerClassName = containerName.substringAfterLast('.') + element = element.withLookupString(outerClassName) + itemText = outerClassName + "." + itemText containerName = containerName.substringBeforeLast('.', FqName.ROOT.toString()) } element = element.withPresentableText(itemText!!) @@ -246,6 +248,7 @@ public class LookupElementFactory( element = element.withPresentableText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderClassifierName(descriptor)) while (container is ClassDescriptor) { + element = element.withLookupString(container.name.asString()) container = container.getContainingDeclaration() } } diff --git a/idea/idea-completion/testData/basic/common/InEmptyImport.kt b/idea/idea-completion/testData/basic/common/InEmptyImport.kt index 9c08e14a2f5..80138385656 100644 --- a/idea/idea-completion/testData/basic/common/InEmptyImport.kt +++ b/idea/idea-completion/testData/basic/common/InEmptyImport.kt @@ -2,7 +2,7 @@ package Test import -// EXIST: java, +// EXIST: java // EXIST_JAVA_ONLY: javax // EXIST_JS_ONLY: jquery, html5 // ABSENT: Array, Integer diff --git a/idea/idea-completion/testData/basic/common/super/QualifierType1.kt b/idea/idea-completion/testData/basic/common/super/QualifierType1.kt new file mode 100644 index 00000000000..a7636fd08df --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierType1.kt @@ -0,0 +1,15 @@ +open class Base + +open class A : Base + +interface I + +class B : A(), I { + fun foo() { + super< + } +} + +// EXIST: A +// EXIST: I +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierType2.kt b/idea/idea-completion/testData/basic/common/super/QualifierType2.kt new file mode 100644 index 00000000000..cf9c4cf1f91 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierType2.kt @@ -0,0 +1,13 @@ +open class A + +interface I + +class B : A(), I { + fun foo() { + super<> + } +} + +// EXIST: A +// EXIST: I +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierType3.kt b/idea/idea-completion/testData/basic/common/super/QualifierType3.kt new file mode 100644 index 00000000000..916d0ebd5d2 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierType3.kt @@ -0,0 +1,13 @@ +open class AA + +interface AI + +class B : AA(), AI { + fun foo() { + super + } +} + +// EXIST: AA +// EXIST: AI +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierType4.kt b/idea/idea-completion/testData/basic/common/super/QualifierType4.kt new file mode 100644 index 00000000000..8fb812c83bf --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierType4.kt @@ -0,0 +1,17 @@ +package p + +open class A + +interface I { + interface Nested +} + +class B : A(), I.Nested { + fun foo() { + super< + } +} + +// EXIST: A +// EXIST: { lookupString: "Nested", allLookupStrings: "I, Nested", itemText: "I.Nested", tailText: " (p)" } +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierType5.kt b/idea/idea-completion/testData/basic/common/super/QualifierType5.kt new file mode 100644 index 00000000000..8738ce47056 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierType5.kt @@ -0,0 +1,16 @@ +package p + +open class A + +interface I { + interface Nested +} + +class B : A(), I.Nested { + fun foo() { + super + } +} + +// EXIST: { lookupString: "Nested", allLookupStrings: "I, Nested", itemText: "I.Nested", tailText: " (p)" } +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierType6.kt b/idea/idea-completion/testData/basic/common/super/QualifierType6.kt new file mode 100644 index 00000000000..5fc925c4260 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierType6.kt @@ -0,0 +1,17 @@ +package p + +open class A + +interface I { + interface Nested1 + interface Nested2 +} + +class B : A(), I.Nested1 { + fun foo() { + super + } +} + +// EXIST: Nested1 +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierTypeAny.kt b/idea/idea-completion/testData/basic/common/super/QualifierTypeAny.kt new file mode 100644 index 00000000000..1a0b7dffe27 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierTypeAny.kt @@ -0,0 +1,11 @@ +interface I + +class B : I { + fun foo() { + super< + } +} + +// EXIST: Any +// EXIST: I +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierTypeAnyInInterface.kt b/idea/idea-completion/testData/basic/common/super/QualifierTypeAnyInInterface.kt new file mode 100644 index 00000000000..024e104c085 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierTypeAnyInInterface.kt @@ -0,0 +1,11 @@ +interface X + +interface I : X { + fun foo() { + super< + } +} + +// EXIST: Any +// EXIST: X +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/super/QualifierTypeGeneric.kt b/idea/idea-completion/testData/basic/common/super/QualifierTypeGeneric.kt new file mode 100644 index 00000000000..cf3bd3a717a --- /dev/null +++ b/idea/idea-completion/testData/basic/common/super/QualifierTypeGeneric.kt @@ -0,0 +1,11 @@ +open class A + +interface I + +class B : A(), I { + fun foo() { + super< + } +} + +// EXIST: { itemText: "A", tailText: " ()" } diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.java deleted file mode 100644 index 3f62165c989..00000000000 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.completion.test; - -import com.google.common.base.Function; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableList; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementPresentation; -import com.intellij.codeInsight.lookup.impl.LookupCellRenderer; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.JBColor; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.idea.test.AstAccessControl; -import org.jetbrains.kotlin.js.resolve.JsPlatform; -import org.jetbrains.kotlin.resolve.TargetPlatform; -import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform; -import org.jetbrains.kotlin.test.InTextDirectivesUtils; -import org.junit.Assert; - -import java.awt.*; -import java.util.*; -import java.util.List; - -/** - * Extract a number of statements about completion from the given text. Those statements - * should be asserted during test execution. - */ -public class ExpectedCompletionUtils { - private ExpectedCompletionUtils() { - } - - public static class CompletionProposal { - public static final String LOOKUP_STRING = "lookupString"; - public static final String PRESENTATION_ITEM_TEXT = "itemText"; - public static final String PRESENTATION_TYPE_TEXT = "typeText"; - public static final String PRESENTATION_TAIL_TEXT = "tailText"; - public static final String PRESENTATION_TEXT_ATTRIBUTES = "attributes"; - public static final Set validKeys = new HashSet( - Arrays.asList(LOOKUP_STRING, PRESENTATION_ITEM_TEXT, PRESENTATION_TYPE_TEXT, PRESENTATION_TAIL_TEXT, - PRESENTATION_TEXT_ATTRIBUTES) - ); - - private final Map map; - - public CompletionProposal(@NotNull String lookupString) { - map = new HashMap(); - map.put(LOOKUP_STRING, lookupString); - } - - public CompletionProposal(@NotNull Map map) { - this.map = map; - for (String key : map.keySet()) { - if (!validKeys.contains(key)){ - throw new RuntimeException("Invalid key '" + key + "'"); - } - } - } - - public CompletionProposal(@NotNull JsonObject json) { - map = new HashMap(); - for (Map.Entry entry : json.entrySet()) { - String key = entry.getKey(); - if (!validKeys.contains(key)) { - throw new RuntimeException("Invalid json property '" + key + "'"); - } - JsonElement value = entry.getValue(); - if (!(value instanceof JsonNull)) { - map.put(key, value.getAsString()); - } - } - } - - public boolean matches(CompletionProposal expectedProposal) { - for (Map.Entry entry : expectedProposal.map.entrySet()) { - if (!entry.getValue().equals(map.get(entry.getKey()))) { - return false; - } - } - return true; - } - - @Override - public String toString() { - JsonObject jsonObject = new JsonObject(); - for (Map.Entry entry : map.entrySet()) { - jsonObject.addProperty(entry.getKey(), entry.getValue()); - } - return jsonObject.toString(); - } - } - - private static final String UNSUPPORTED_PLATFORM_MESSAGE = String.format("Only %s and %s platforms are supported", JvmPlatform.INSTANCE$, JsPlatform.INSTANCE$); - - private static final String EXIST_LINE_PREFIX = "EXIST:"; - - private static final String ABSENT_LINE_PREFIX = "ABSENT:"; - private static final String ABSENT_JS_LINE_PREFIX = "ABSENT_JS:"; - private static final String ABSENT_JAVA_LINE_PREFIX = "ABSENT_JAVA:"; - - private static final String EXIST_JAVA_ONLY_LINE_PREFIX = "EXIST_JAVA_ONLY:"; - private static final String EXIST_JS_ONLY_LINE_PREFIX = "EXIST_JS_ONLY:"; - - private static final String NUMBER_LINE_PREFIX = "NUMBER:"; - private static final String NUMBER_JS_LINE_PREFIX = "NUMBER_JS:"; - private static final String NUMBER_JAVA_LINE_PREFIX = "NUMBER_JAVA:"; - - private static final String NOTHING_ELSE_PREFIX = "NOTHING_ELSE"; - - private static final String INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"; - private static final String WITH_ORDER_PREFIX = "WITH_ORDER"; - private static final String AUTOCOMPLETE_SETTING_PREFIX = "AUTOCOMPLETE_SETTING:"; - - public static final String RUNTIME_TYPE = "RUNTIME_TYPE:"; - - public static final List KNOWN_PREFIXES = ImmutableList.of( - EXIST_LINE_PREFIX, - ABSENT_LINE_PREFIX, - ABSENT_JS_LINE_PREFIX, - ABSENT_JAVA_LINE_PREFIX, - EXIST_JAVA_ONLY_LINE_PREFIX, - EXIST_JS_ONLY_LINE_PREFIX, - NUMBER_LINE_PREFIX, - NUMBER_JS_LINE_PREFIX, - NUMBER_JAVA_LINE_PREFIX, - INVOCATION_COUNT_PREFIX, - WITH_ORDER_PREFIX, - AUTOCOMPLETE_SETTING_PREFIX, - NOTHING_ELSE_PREFIX, - RUNTIME_TYPE, - AstAccessControl.INSTANCE$.getALLOW_AST_ACCESS_DIRECTIVE()); - - @NotNull - public static CompletionProposal[] itemsShouldExist(String fileText, @Nullable TargetPlatform platform) { - if (platform == null) { - return processProposalAssertions(fileText, EXIST_LINE_PREFIX); - } - else if (platform == JvmPlatform.INSTANCE$) { - return processProposalAssertions(fileText, EXIST_LINE_PREFIX, EXIST_JAVA_ONLY_LINE_PREFIX); - } - else if (platform == JsPlatform.INSTANCE$) { - return processProposalAssertions(fileText, EXIST_LINE_PREFIX, EXIST_JS_ONLY_LINE_PREFIX); - } - else { - throw new IllegalArgumentException(UNSUPPORTED_PLATFORM_MESSAGE); - } - } - - @NotNull - public static CompletionProposal[] itemsShouldAbsent(String fileText, @Nullable TargetPlatform platform) { - if (platform == null) { - return processProposalAssertions(fileText, ABSENT_LINE_PREFIX); - } - else if (platform == JvmPlatform.INSTANCE$) { - return processProposalAssertions(fileText, ABSENT_LINE_PREFIX, ABSENT_JAVA_LINE_PREFIX, EXIST_JS_ONLY_LINE_PREFIX); - } - else if (platform == JsPlatform.INSTANCE$) { - return processProposalAssertions(fileText, ABSENT_LINE_PREFIX, ABSENT_JS_LINE_PREFIX, EXIST_JAVA_ONLY_LINE_PREFIX); - } - else { - throw new IllegalArgumentException(UNSUPPORTED_PLATFORM_MESSAGE); - } - } - - public static CompletionProposal[] processProposalAssertions(String fileText, String... prefixes) { - Collection proposals = new ArrayList(); - for (String proposalStr : InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, prefixes)) { - if (proposalStr.startsWith("{")){ - JsonParser parser = new JsonParser(); - JsonElement json = parser.parse(proposalStr); - proposals.add(new CompletionProposal((JsonObject) json)); - } - else if (proposalStr.startsWith("\"") && proposalStr.endsWith("\"")) { - proposals.add(new CompletionProposal(proposalStr.substring(1, proposalStr.length() - 1))); - } - else{ - for(String item : proposalStr.split(",")){ - proposals.add(new CompletionProposal(item.trim())); - } - } - } - - return ArrayUtil.toObjectArray(proposals, CompletionProposal.class); - } - - @Nullable - public static Integer getExpectedNumber(String fileText, @Nullable TargetPlatform platform) { - if (platform == null) { - return InTextDirectivesUtils.getPrefixedInt(fileText, NUMBER_LINE_PREFIX); - } - else if (platform == JvmPlatform.INSTANCE$) { - return getPlatformExpectedNumber(fileText, NUMBER_JAVA_LINE_PREFIX); - } - else if (platform == JsPlatform.INSTANCE$) { - return getPlatformExpectedNumber(fileText, NUMBER_JS_LINE_PREFIX); - } - else { - throw new IllegalArgumentException(UNSUPPORTED_PLATFORM_MESSAGE); - } - } - - public static boolean isNothingElseExpected(String fileText) { - return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, NOTHING_ELSE_PREFIX).isEmpty(); - } - - @Nullable - public static Integer getInvocationCount(String fileText) { - return InTextDirectivesUtils.getPrefixedInt(fileText, INVOCATION_COUNT_PREFIX); - } - - @Nullable - public static Boolean getAutocompleteSetting(String fileText) { - return InTextDirectivesUtils.getPrefixedBoolean(fileText, AUTOCOMPLETE_SETTING_PREFIX); - } - - public static boolean isWithOrder(String fileText) { - return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, WITH_ORDER_PREFIX).isEmpty(); - } - - public static void assertDirectivesValid(String fileText) { - InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES); - } - - public static void assertContainsRenderedItems(CompletionProposal[] expected, LookupElement[] items, boolean checkOrder, boolean nothingElse) { - List itemsInformation = getItemsInformation(items); - String allItemsString = listToString(itemsInformation); - - Set leftItems = nothingElse ? new LinkedHashSet(itemsInformation) : null; - - int indexOfPrevious = Integer.MIN_VALUE; - - for (CompletionProposal expectedProposal : expected) { - boolean isFound = false; - - for (int index = 0; index < itemsInformation.size(); index++) { - CompletionProposal proposal = itemsInformation.get(index); - - if (proposal.matches(expectedProposal)) { - isFound = true; - - Assert.assertTrue("Invalid order of existent elements in " + allItemsString, - !checkOrder || index > indexOfPrevious); - indexOfPrevious = index; - - if (leftItems != null) { - leftItems.remove(proposal); - } - - break; - } - } - - if (!isFound) { - if (allItemsString.isEmpty()) { - Assert.fail("Completion is empty but " + expectedProposal + " is expected"); - } - else { - Assert.fail("Expected " + expectedProposal + " not found in:\n" + allItemsString); - } - } - } - - if (leftItems != null && !leftItems.isEmpty()) { - Assert.fail("No items not mentioned in EXIST directives expected but some found:\n" + listToString(leftItems)); - } - } - - private static Integer getPlatformExpectedNumber(String fileText, String platformNumberPrefix) { - Integer prefixedInt = InTextDirectivesUtils.getPrefixedInt(fileText, platformNumberPrefix); - if (prefixedInt != null) { - Assert.assertNull(String.format("There shouldn't be %s and %s prefixes set in same time", NUMBER_LINE_PREFIX, - platformNumberPrefix), - InTextDirectivesUtils.getPrefixedInt(fileText, NUMBER_LINE_PREFIX)); - return prefixedInt; - } - - return InTextDirectivesUtils.getPrefixedInt(fileText, NUMBER_LINE_PREFIX); - } - - public static void assertNotContainsRenderedItems(CompletionProposal[] unexpected, LookupElement[] items) { - List itemsInformation = getItemsInformation(items); - String allItemsString = listToString(itemsInformation); - - for (CompletionProposal unexpectedProposal : unexpected) { - for (CompletionProposal proposal : itemsInformation) { - Assert.assertFalse("Unexpected '" + unexpectedProposal + "' presented in " + allItemsString, - proposal.matches(unexpectedProposal)); - } - } - } - - public static List getItemsInformation(LookupElement[] items) { - LookupElementPresentation presentation = new LookupElementPresentation(); - - List result = new ArrayList(); - if (items != null) { - for (LookupElement item : items) { - item.renderElement(presentation); - Map map = new HashMap(); - map.put(CompletionProposal.LOOKUP_STRING, item.getLookupString()); - if (presentation.getItemText() != null){ - map.put(CompletionProposal.PRESENTATION_ITEM_TEXT, presentation.getItemText()); - map.put(CompletionProposal.PRESENTATION_TEXT_ATTRIBUTES, textAttributes(presentation)); - } - if (presentation.getTypeText() != null){ - map.put(CompletionProposal.PRESENTATION_TYPE_TEXT, presentation.getTypeText()); - } - if (presentation.getTailText() != null){ - map.put(CompletionProposal.PRESENTATION_TAIL_TEXT, presentation.getTailText()); - } - result.add(new ExpectedCompletionUtils.CompletionProposal(map)); - } - } - - return result; - } - - private static String textAttributes(LookupElementPresentation presentation) { - StringBuilder builder = new StringBuilder(); - if (presentation.isItemTextBold()) { - builder.append("bold"); - } - if (presentation.isItemTextUnderlined()) { - if (builder.length() > 0) builder.append(" "); - builder.append("underlined"); - } - Color foreground = presentation.getItemTextForeground(); - if (!foreground.equals(JBColor.foreground())) { - assert foreground.equals(LookupCellRenderer.getGrayedForeground(false)); - if (builder.length() > 0) builder.append(" "); - builder.append("grayed"); - } - if (presentation.isStrikeout()) { - if (builder.length() > 0) builder.append(" "); - builder.append("strikeout"); - } - return builder.toString(); - } - - public static String listToString(Collection 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/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.kt b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.kt new file mode 100644 index 00000000000..478bd236b23 --- /dev/null +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.kt @@ -0,0 +1,321 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.completion.test + +import com.google.common.collect.ImmutableList +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementPresentation +import com.intellij.codeInsight.lookup.impl.LookupCellRenderer +import com.intellij.ui.JBColor +import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.idea.test.AstAccessControl +import org.jetbrains.kotlin.js.resolve.JsPlatform +import org.jetbrains.kotlin.resolve.TargetPlatform +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.junit.Assert +import java.util.* + +/** + * Extract a number of statements about completion from the given text. Those statements + * should be asserted during test execution. + */ +public object ExpectedCompletionUtils { + + public class CompletionProposal { + private val map: Map + + public constructor(lookupString: String) { + map = HashMap() + map.put(LOOKUP_STRING, lookupString) + } + + public constructor(map: MutableMap) { + this.map = map + for (key in map.keySet()) { + if (key !in validKeys) { + throw RuntimeException("Invalid key '$key'") + } + } + } + + public constructor(json: JsonObject) { + map = HashMap() + for (entry in json.entrySet()) { + val key = entry.getKey() + if (key !in validKeys) { + throw RuntimeException("Invalid json property '$key'") + } + val value = entry.value + if (value !is JsonNull) { + map.put(key, value.asString) + } + } + } + + public fun matches(expectedProposal: CompletionProposal): Boolean + = expectedProposal.map.entrySet().none { it.value != map[it.key] } + + override fun toString(): String { + val jsonObject = JsonObject() + for ((key, value) in map) { + jsonObject.addProperty(key, value) + } + return jsonObject.toString() + } + + companion object { + public val LOOKUP_STRING: String = "lookupString" + public val ALL_LOOKUP_STRINGS: String = "allLookupStrings" + public val PRESENTATION_ITEM_TEXT: String = "itemText" + public val PRESENTATION_TYPE_TEXT: String = "typeText" + public val PRESENTATION_TAIL_TEXT: String = "tailText" + public val PRESENTATION_TEXT_ATTRIBUTES: String = "attributes" + public val validKeys: Set = setOf(LOOKUP_STRING, ALL_LOOKUP_STRINGS, PRESENTATION_ITEM_TEXT, PRESENTATION_TYPE_TEXT, PRESENTATION_TAIL_TEXT, PRESENTATION_TEXT_ATTRIBUTES) + } + } + + private val UNSUPPORTED_PLATFORM_MESSAGE = "Only $JvmPlatform and $JsPlatform platforms are supported" + + private val EXIST_LINE_PREFIX = "EXIST:" + + private val ABSENT_LINE_PREFIX = "ABSENT:" + private val ABSENT_JS_LINE_PREFIX = "ABSENT_JS:" + private val ABSENT_JAVA_LINE_PREFIX = "ABSENT_JAVA:" + + private val EXIST_JAVA_ONLY_LINE_PREFIX = "EXIST_JAVA_ONLY:" + private val EXIST_JS_ONLY_LINE_PREFIX = "EXIST_JS_ONLY:" + + private val NUMBER_LINE_PREFIX = "NUMBER:" + private val NUMBER_JS_LINE_PREFIX = "NUMBER_JS:" + private val NUMBER_JAVA_LINE_PREFIX = "NUMBER_JAVA:" + + private val NOTHING_ELSE_PREFIX = "NOTHING_ELSE" + + private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:" + private val WITH_ORDER_PREFIX = "WITH_ORDER" + private val AUTOCOMPLETE_SETTING_PREFIX = "AUTOCOMPLETE_SETTING:" + + public val RUNTIME_TYPE: String = "RUNTIME_TYPE:" + + public val KNOWN_PREFIXES: List = ImmutableList.of( + EXIST_LINE_PREFIX, + ABSENT_LINE_PREFIX, + ABSENT_JS_LINE_PREFIX, + ABSENT_JAVA_LINE_PREFIX, + EXIST_JAVA_ONLY_LINE_PREFIX, + EXIST_JS_ONLY_LINE_PREFIX, + NUMBER_LINE_PREFIX, + NUMBER_JS_LINE_PREFIX, + NUMBER_JAVA_LINE_PREFIX, + INVOCATION_COUNT_PREFIX, + WITH_ORDER_PREFIX, + AUTOCOMPLETE_SETTING_PREFIX, + NOTHING_ELSE_PREFIX, + RUNTIME_TYPE, + AstAccessControl.ALLOW_AST_ACCESS_DIRECTIVE) + + public fun itemsShouldExist(fileText: String, platform: TargetPlatform?): Array { + return when (platform) { + JvmPlatform -> processProposalAssertions(fileText, EXIST_LINE_PREFIX, EXIST_JAVA_ONLY_LINE_PREFIX) + JsPlatform -> processProposalAssertions(fileText, EXIST_LINE_PREFIX, EXIST_JS_ONLY_LINE_PREFIX) + null -> processProposalAssertions(fileText, EXIST_LINE_PREFIX) + else -> throw IllegalArgumentException(UNSUPPORTED_PLATFORM_MESSAGE) + } + } + + public fun itemsShouldAbsent(fileText: String, platform: TargetPlatform?): Array { + return when (platform) { + JvmPlatform -> processProposalAssertions(fileText, ABSENT_LINE_PREFIX, ABSENT_JAVA_LINE_PREFIX, EXIST_JS_ONLY_LINE_PREFIX) + JsPlatform -> processProposalAssertions(fileText, ABSENT_LINE_PREFIX, ABSENT_JS_LINE_PREFIX, EXIST_JAVA_ONLY_LINE_PREFIX) + null -> processProposalAssertions(fileText, ABSENT_LINE_PREFIX) + else -> throw IllegalArgumentException(UNSUPPORTED_PLATFORM_MESSAGE) + } + } + + public fun processProposalAssertions(fileText: String, vararg prefixes: String): Array { + val proposals = ArrayList() + for (proposalStr in InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, *prefixes)) { + if (proposalStr.startsWith("{")) { + val parser = JsonParser() + val json = parser.parse(proposalStr) + proposals.add(CompletionProposal(json as JsonObject)) + } + else if (proposalStr.startsWith("\"") && proposalStr.endsWith("\"")) { + proposals.add(CompletionProposal(proposalStr.substring(1, proposalStr.length() - 1))) + } + else { + for (item in proposalStr.split(",")) { + proposals.add(CompletionProposal(item.trim())) + } + } + } + + return ArrayUtil.toObjectArray(proposals, javaClass()) + } + + public fun getExpectedNumber(fileText: String, platform: TargetPlatform?): Int? { + return when (platform) { + null -> InTextDirectivesUtils.getPrefixedInt(fileText, NUMBER_LINE_PREFIX) + JvmPlatform -> getPlatformExpectedNumber(fileText, NUMBER_JAVA_LINE_PREFIX) + JsPlatform -> getPlatformExpectedNumber(fileText, NUMBER_JS_LINE_PREFIX) + else -> throw IllegalArgumentException(UNSUPPORTED_PLATFORM_MESSAGE) + } + } + + public fun isNothingElseExpected(fileText: String): Boolean { + return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, NOTHING_ELSE_PREFIX).isEmpty() + } + + public fun getInvocationCount(fileText: String): Int? { + return InTextDirectivesUtils.getPrefixedInt(fileText, INVOCATION_COUNT_PREFIX) + } + + public fun getAutocompleteSetting(fileText: String): Boolean? { + return InTextDirectivesUtils.getPrefixedBoolean(fileText, AUTOCOMPLETE_SETTING_PREFIX) + } + + public fun isWithOrder(fileText: String): Boolean { + return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, WITH_ORDER_PREFIX).isEmpty() + } + + public fun assertDirectivesValid(fileText: String) { + InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES) + } + + public fun assertContainsRenderedItems(expected: Array, items: Array, checkOrder: Boolean, nothingElse: Boolean) { + val itemsInformation = getItemsInformation(items) + val allItemsString = listToString(itemsInformation) + + val leftItems = if (nothingElse) LinkedHashSet(itemsInformation) else null + + var indexOfPrevious = Integer.MIN_VALUE + + for (expectedProposal in expected) { + var isFound = false + + for (index in itemsInformation.indices) { + val proposal = itemsInformation.get(index) + + if (proposal.matches(expectedProposal)) { + isFound = true + + Assert.assertTrue("Invalid order of existent elements in $allItemsString", + !checkOrder || index > indexOfPrevious) + indexOfPrevious = index + + leftItems?.remove(proposal) + + break + } + } + + if (!isFound) { + if (allItemsString.isEmpty()) { + Assert.fail("Completion is empty but $expectedProposal is expected") + } + else { + Assert.fail("Expected $expectedProposal not found in:\n$allItemsString") + } + } + } + + if (leftItems != null && !leftItems.isEmpty()) { + Assert.fail("No items not mentioned in EXIST directives expected but some found:\n" + listToString(leftItems)) + } + } + + private fun getPlatformExpectedNumber(fileText: String, platformNumberPrefix: String): Int? { + val prefixedInt = InTextDirectivesUtils.getPrefixedInt(fileText, platformNumberPrefix) + if (prefixedInt != null) { + Assert.assertNull("There shouldn't be $NUMBER_LINE_PREFIX and $platformNumberPrefix prefixes set in same time", + InTextDirectivesUtils.getPrefixedInt(fileText, NUMBER_LINE_PREFIX)) + return prefixedInt + } + + return InTextDirectivesUtils.getPrefixedInt(fileText, NUMBER_LINE_PREFIX) + } + + public fun assertNotContainsRenderedItems(unexpected: Array, items: Array) { + val itemsInformation = getItemsInformation(items) + val allItemsString = listToString(itemsInformation) + + for (unexpectedProposal in unexpected) { + for (proposal in itemsInformation) { + Assert.assertFalse("Unexpected '$unexpectedProposal' presented in $allItemsString", + proposal.matches(unexpectedProposal)) + } + } + } + + public fun getItemsInformation(items: Array): List { + val presentation = LookupElementPresentation() + + val result = ArrayList(items.size()) + for (item in items) { + item.renderElement(presentation) + + val map = HashMap() + map.put(CompletionProposal.LOOKUP_STRING, item.lookupString) + + map.put(CompletionProposal.ALL_LOOKUP_STRINGS, item.allLookupStrings.sort().joinToString()) + + if (presentation.itemText != null) { + map.put(CompletionProposal.PRESENTATION_ITEM_TEXT, presentation.itemText) + map.put(CompletionProposal.PRESENTATION_TEXT_ATTRIBUTES, textAttributes(presentation)) + } + + if (presentation.typeText != null) { + map.put(CompletionProposal.PRESENTATION_TYPE_TEXT, presentation.typeText) + } + + if (presentation.tailText != null) { + map.put(CompletionProposal.PRESENTATION_TAIL_TEXT, presentation.tailText) + } + + result.add(ExpectedCompletionUtils.CompletionProposal(map)) + } + return result + } + + private fun textAttributes(presentation: LookupElementPresentation): String { + return StringBuilder { + if (presentation.isItemTextBold) { + append("bold") + } + if (presentation.isItemTextUnderlined) { + if (length() > 0) append(" ") + append("underlined") + } + val foreground = presentation.itemTextForeground + if (foreground != JBColor.foreground()) { + assert(foreground == LookupCellRenderer.getGrayedForeground(false)) + if (length() > 0) append(" ") + append("grayed") + } + if (presentation.isStrikeout) { + if (length() > 0) append(" ") + append("strikeout") + } + }.toString() + } + + public fun listToString(items: Collection): String = items.joinToString("\n") +} diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index 4d0d6628f4a..3b3a21103a0 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -1918,6 +1918,69 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes } } + @TestMetadata("idea/idea-completion/testData/basic/common/super") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Super extends AbstractJSBasicCompletionTest { + public void testAllFilesPresentInSuper() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/super"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("QualifierType1.kt") + public void testQualifierType1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType1.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType2.kt") + public void testQualifierType2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType2.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType3.kt") + public void testQualifierType3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType3.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType4.kt") + public void testQualifierType4() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType4.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType5.kt") + public void testQualifierType5() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType5.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType6.kt") + public void testQualifierType6() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType6.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierTypeAny.kt") + public void testQualifierTypeAny() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierTypeAny.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierTypeAnyInInterface.kt") + public void testQualifierTypeAnyInInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierTypeAnyInInterface.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierTypeGeneric.kt") + public void testQualifierTypeGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierTypeGeneric.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/typeArgsOrNot") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 14fb8fb179d..dd2cf726df2 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -1918,6 +1918,69 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT } } + @TestMetadata("idea/idea-completion/testData/basic/common/super") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Super extends AbstractJvmBasicCompletionTest { + public void testAllFilesPresentInSuper() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/super"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("QualifierType1.kt") + public void testQualifierType1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType1.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType2.kt") + public void testQualifierType2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType2.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType3.kt") + public void testQualifierType3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType3.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType4.kt") + public void testQualifierType4() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType4.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType5.kt") + public void testQualifierType5() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType5.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierType6.kt") + public void testQualifierType6() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierType6.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierTypeAny.kt") + public void testQualifierTypeAny() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierTypeAny.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierTypeAnyInInterface.kt") + public void testQualifierTypeAnyInInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierTypeAnyInInterface.kt"); + doTest(fileName); + } + + @TestMetadata("QualifierTypeGeneric.kt") + public void testQualifierTypeGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/super/QualifierTypeGeneric.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/typeArgsOrNot") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)