KT-5876 Code completion should never auto-insert item which requires adding import

#KT-5876 Fixed
This commit is contained in:
Valentin Kipyatkov
2014-10-08 13:24:37 +04:00
parent a124827c4e
commit eabcbb00ab
14 changed files with 138 additions and 40 deletions
@@ -36,9 +36,21 @@ public final class InTextDirectivesUtils {
@Nullable
public static Integer getPrefixedInt(String fileText, String prefix) {
String[] numberStrings = findArrayWithPrefixes(fileText, prefix);
if (numberStrings.length > 0) {
return Integer.parseInt(numberStrings[0]);
String[] strings = findArrayWithPrefixes(fileText, prefix);
if (strings.length > 0) {
assert strings.length == 1;
return Integer.parseInt(strings[0]);
}
return null;
}
@Nullable
public static Boolean getPrefixedBoolean(String fileText, String prefix) {
String[] strings = findArrayWithPrefixes(fileText, prefix);
if (strings.length > 0) {
assert strings.length == 1;
return Boolean.parseBoolean(strings[0]);
}
return null;
@@ -60,7 +60,6 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
protected val prefixMatcher: PrefixMatcher = this.resultSet.getPrefixMatcher()
protected val collector: LookupElementsCollector = LookupElementsCollector(prefixMatcher, resolveSession, { isVisibleDescriptor(it) })
protected var anythingAdded: Boolean = false
protected val project: Project = position.getProject()
protected val indicesHelper: KotlinIndicesHelper = KotlinIndicesHelper(project)
@@ -77,16 +76,13 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
}
protected fun flushToResultSet() {
if (!collector.isEmpty) {
anythingAdded = true
}
collector.flushToResultSet(resultSet)
}
public fun complete(): Boolean {
doComplete()
flushToResultSet()
return anythingAdded
return !collector.isResultEmpty
}
protected abstract fun doComplete()
@@ -157,11 +153,11 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
private fun addNonImported() {
if (shouldRunTopLevelCompletion()) {
TypesCompletion(parameters, resolveSession, prefixMatcher).addAllTypes(collector)
collector.addDescriptorElements(getKotlinTopLevelDeclarations())
collector.addDescriptorElements(getKotlinTopLevelDeclarations(), suppressAutoInsertion = true)
}
if (shouldRunExtensionsCompletion()) {
collector.addDescriptorElements(getKotlinExtensions())
collector.addDescriptorElements(getKotlinExtensions(), suppressAutoInsertion = true)
}
}
@@ -196,7 +192,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
private fun addReferenceVariants(filterCondition: (DeclarationDescriptor) -> Boolean = { true }) {
val descriptors = TipsManager.getReferenceVariants(jetReference!!.expression, bindingContext!!)
collector.addDescriptorElements(descriptors.filter { filterCondition(it) })
collector.addDescriptorElements(descriptors.filter { filterCondition(it) }, suppressAutoInsertion = false)
}
}
@@ -35,23 +35,35 @@ class LookupElementsCollector(private val prefixMatcher: PrefixMatcher,
private val elements = ArrayList<LookupElement>()
public fun flushToResultSet(resultSet: CompletionResultSet) {
resultSet.addAllElements(elements)
elements.clear()
}
public val isEmpty: Boolean
get() = elements.isEmpty()
public fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>) {
for (descriptor in descriptors) {
addDescriptorElements(descriptor)
if (!elements.isEmpty()) {
resultSet.addAllElements(elements)
elements.clear()
isResultEmpty = false
}
}
public fun addDescriptorElements(descriptor: DeclarationDescriptor) {
public var isResultEmpty: Boolean = true
private set
public fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>,
suppressAutoInsertion: Boolean // auto-insertion suppression is used for elements that require adding an import
) {
for (descriptor in descriptors) {
addDescriptorElements(descriptor, suppressAutoInsertion)
}
}
public fun addDescriptorElements(descriptor: DeclarationDescriptor, suppressAutoInsertion: Boolean) {
if (!descriptorFilter(descriptor)) return
addElement(DescriptorLookupConverter.createLookupElement(resolveSession, descriptor))
run {
var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, descriptor)
if (suppressAutoInsertion &&
elements.isEmpty() && isResultEmpty /* without these checks we would get duplicated items */) {
lookupElement = lookupElement.suppressAutoInsertion()
}
addElement(lookupElement)
}
// add special item for function with one argument of function type with more than one parameter
if (descriptor is FunctionDescriptor) {
@@ -37,11 +37,13 @@ import org.jetbrains.jet.plugin.search.searchScopeForSourceElementDependencies
class TypesCompletion(val parameters: CompletionParameters, val resolveSession: ResolveSessionForBodies, val prefixMatcher: PrefixMatcher) {
fun addAllTypes(result: LookupElementsCollector) {
result.addDescriptorElements(KotlinBuiltIns.getInstance().getNonPhysicalClasses())
result.addDescriptorElements(KotlinBuiltIns.getInstance().getNonPhysicalClasses().filter { prefixMatcher.prefixMatches(it.getName().asString()) },
suppressAutoInsertion = true)
val project = parameters.getOriginalFile().getProject()
val searchScope = searchScopeForSourceElementDependencies(parameters.getOriginalFile()) ?: return
result.addDescriptorElements(KotlinIndicesHelper(project).getClassDescriptors({ prefixMatcher.prefixMatches(it) }, resolveSession, searchScope))
result.addDescriptorElements(KotlinIndicesHelper(project).getClassDescriptors({ prefixMatcher.prefixMatches(it) }, resolveSession, searchScope),
suppressAutoInsertion = true)
if (!ProjectStructureUtil.isJsKotlinModule(parameters.getOriginalFile() as JetFile)) {
addAdaptedJavaCompletion(result)
@@ -63,7 +65,7 @@ class TypesCompletion(val parameters: CompletionParameters, val resolveSession:
override fun handleInsert(context: InsertionContext) {
JetJavaClassInsertHandler.handleInsert(context, lookupElement)
}
})
}.suppressAutoInsertion())
}
}
})
@@ -79,7 +81,8 @@ class TypesCompletion(val parameters: CompletionParameters, val resolveSession:
if (JetFromJavaDescriptorHelper.getCompiledClassKind(aClass) != ClassKind.CLASS_OBJECT) {
val qualifiedName = aClass.getQualifiedName()
if (qualifiedName != null) {
result.addDescriptorElements(ResolveSessionUtils.getClassDescriptorsByFqName(resolveSession.getModuleDescriptor(), FqName(qualifiedName)))
result.addDescriptorElements(ResolveSessionUtils.getClassDescriptorsByFqName(resolveSession.getModuleDescriptor(), FqName(qualifiedName)),
suppressAutoInsertion = true)
}
}
@@ -0,0 +1,7 @@
fun foo() {
PrintS<caret>
}
// AUTOCOMPLETE_SETTING: true
// EXIST_JAVA_ONLY: PrintStream
// NUMBER_JAVA: 1
@@ -0,0 +1,5 @@
package second
public class NotImportedClass
// ALLOW_AST_ACCESS
@@ -0,0 +1,7 @@
package first
class A : NotImported<caret>
// AUTOCOMPLETE_SETTING: true
// EXIST: NotImportedClass
// NUMBER: 1
@@ -20,23 +20,47 @@ import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.jet.plugin.project.TargetPlatform
import org.junit.Assert
import org.jetbrains.jet.completion.ExpectedCompletionUtils
import com.intellij.codeInsight.CodeInsightSettings
fun testCompletion(fileText: String, platform: TargetPlatform?, complete: (Int) -> Array<LookupElement>?, defaultInvocationCount: Int = 0) {
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
val items = complete(invocationCount) ?: array()
testWithAutoCompleteSetting(fileText) {
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
val items = complete(invocationCount) ?: array()
ExpectedCompletionUtils.assertDirectivesValid(fileText)
ExpectedCompletionUtils.assertDirectivesValid(fileText)
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
val itemsNumber = ExpectedCompletionUtils.getExpectedNumber(fileText, platform)
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
val itemsNumber = ExpectedCompletionUtils.getExpectedNumber(fileText, platform)
Assert.assertTrue("Should be some assertions about completion", expected.size != 0 || unexpected.size != 0 || itemsNumber != null)
ExpectedCompletionUtils.assertContainsRenderedItems(expected, items, ExpectedCompletionUtils.isWithOrder(fileText))
ExpectedCompletionUtils.assertNotContainsRenderedItems(unexpected, items)
Assert.assertTrue("Should be some assertions about completion", expected.size != 0 || unexpected.size != 0 || itemsNumber != null)
ExpectedCompletionUtils.assertContainsRenderedItems(expected, items, ExpectedCompletionUtils.isWithOrder(fileText))
ExpectedCompletionUtils.assertNotContainsRenderedItems(unexpected, items)
if (itemsNumber != null) {
val expectedItems = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items))
Assert.assertEquals("Invalid number of completion items: ${expectedItems}", itemsNumber, items.size)
if (itemsNumber != null) {
val expectedItems = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items))
Assert.assertEquals("Invalid number of completion items: ${expectedItems}", itemsNumber, items.size)
}
}
}
private fun testWithAutoCompleteSetting(fileText: String, doTest: () -> Unit) {
val autoComplete = ExpectedCompletionUtils.getAutocompleteSetting(fileText)
if (autoComplete == null) {
doTest()
return
}
val settings = CodeInsightSettings.getInstance()
val oldValue1 = settings.AUTOCOMPLETE_ON_CODE_COMPLETION
val oldValue2 = settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION
try {
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = autoComplete
settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = autoComplete
doTest()
}
finally {
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = oldValue1
settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = oldValue2
}
}
@@ -115,6 +115,7 @@ public class ExpectedCompletionUtils {
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 List<String> KNOWN_PREFIXES = ImmutableList.of(
EXIST_LINE_PREFIX,
@@ -128,6 +129,7 @@ public class ExpectedCompletionUtils {
NUMBER_JAVA_LINE_PREFIX,
INVOCATION_COUNT_PREFIX,
WITH_ORDER_PREFIX,
AUTOCOMPLETE_SETTING_PREFIX,
AstAccessControl.INSTANCE$.getALLOW_AST_ACCESS_DIRECTIVE());
@NotNull
@@ -204,6 +206,11 @@ public class ExpectedCompletionUtils {
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.getPrefixedInt(fileText, WITH_ORDER_PREFIX) != null;
}
@@ -420,6 +420,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("NoAutoInsertionOfNotImported.kt")
public void testNoAutoInsertionOfNotImported() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoInsertionOfNotImported.kt");
doTest(fileName);
}
@TestMetadata("NoAutoPopupAfterNumberLiteral.kt")
public void testNoAutoPopupAfterNumberLiteral() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoPopupAfterNumberLiteral.kt");
@@ -420,6 +420,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("NoAutoInsertionOfNotImported.kt")
public void testNoAutoInsertionOfNotImported() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoInsertionOfNotImported.kt");
doTest(fileName);
}
@TestMetadata("NoAutoPopupAfterNumberLiteral.kt")
public void testNoAutoPopupAfterNumberLiteral() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoPopupAfterNumberLiteral.kt");
@@ -112,6 +112,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
doTest(fileName);
}
@TestMetadata("NoAutoInsertionOfNotImported")
public void testNoAutoInsertionOfNotImported() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NoAutoInsertionOfNotImported/");
doTest(fileName);
}
@TestMetadata("NotImportedExtensionForImplicitReceiver")
public void testNotImportedExtensionForImplicitReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/");
@@ -67,6 +67,10 @@ public class CompletionMultifileHandlerTest extends KotlinCompletionTestCase {
configureByFiles(null, fileName + "-1.kt", fileName + "-2.kt");
complete(2);
if (myItems != null) {
assertTrue("Multiple items in completion", myItems.length == 1);
selectItem(myItems[0]);
}
checkResultByFile(fileName + ".kt.after");
}
@@ -17,10 +17,13 @@
package org.jetbrains.jet.plugin.quickfix;
import com.intellij.testFramework.TestDataPath;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.junit.runner.RunWith;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import java.io.File;
import java.util.regex.Pattern;