diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt index d817de5dae5..5fa5d2bc370 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt @@ -19,23 +19,14 @@ package org.jetbrains.kotlin.idea.imports import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade -import org.jetbrains.kotlin.idea.util.ImportInsertHelper -import org.jetbrains.kotlin.idea.util.getFileResolutionScope -import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtReferenceExpression -import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor -import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier import org.jetbrains.kotlin.types.KotlinType import java.util.* @@ -97,117 +88,3 @@ fun KtReferenceExpression.getImportableTargets(bindingContext: BindingContext): return targets.map { it.getImportableDescriptor() }.toSet() } -fun prepareOptimizedImports( - file: KtFile, - descriptorsToImport: Collection, - nameCountToUseStarImport: Int, - nameCountToUseStarImportForMembers: Int, - isInPackagesToUseStarImport: (FqName) -> Boolean -): List? { - val importInsertHelper = ImportInsertHelper.getInstance(file.project) - val aliasImports = buildAliasImportMap(file) - - val importsToGenerate = HashSet() - - val descriptorsByParentFqName = hashMapOf>() - for (descriptor in descriptorsToImport) { - val fqName = descriptor.importableFqName!! - val container = descriptor.containingDeclaration - val parentFqName = fqName.parent() - val canUseStarImport = when { - parentFqName.isRoot -> false - (container as? ClassDescriptor)?.kind == ClassKind.OBJECT -> false - else -> true - } - if (canUseStarImport) { - val descriptors = descriptorsByParentFqName.getOrPut(parentFqName) { hashSetOf() } - descriptors.add(descriptor) - } - else { - importsToGenerate.add(ImportPath(fqName, false)) - } - } - - val classNamesToCheck = HashSet() - - fun isImportedByDefault(fqName: FqName) = importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file) - - for (parentFqName in descriptorsByParentFqName.keys) { - val descriptors = descriptorsByParentFqName[parentFqName]!! - val fqNames = descriptors.map { it.importableFqName!! }.toSet() - val isMember = descriptors.first().containingDeclaration is ClassDescriptor - val nameCountToUseStar = if (isMember) - nameCountToUseStarImportForMembers - else - nameCountToUseStarImport - val explicitImports = fqNames.size < nameCountToUseStar && !isInPackagesToUseStarImport(parentFqName) - if (explicitImports) { - for (fqName in fqNames) { - if (!isImportedByDefault(fqName)) { - importsToGenerate.add(ImportPath(fqName, false)) - } - } - } - else { - for (descriptor in descriptors) { - if (descriptor is ClassDescriptor) { - classNamesToCheck.add(descriptor.importableFqName!!) - } - } - - if (!fqNames.all(::isImportedByDefault)) { - importsToGenerate.add(ImportPath(parentFqName, true)) - } - } - } - - // now check that there are no conflicts and all classes are really imported - val fileWithImportsText = buildString { - append("package ").append(file.packageFqName.toUnsafe().render()).append("\n") - importsToGenerate.filter { it.isAllUnder }.map { "import " + it.pathStr }.joinTo(this, "\n") - } - val fileWithImports = KtPsiFactory(file).createAnalyzableFile("Dummy.kt", fileWithImportsText, file) - val scope = fileWithImports.getResolutionFacade().getFileResolutionScope(fileWithImports) - - for (fqName in classNamesToCheck) { - if (scope.findClassifier(fqName.shortName(), NoLookupLocation.FROM_IDE)?.importableFqName != fqName) { - // add explicit import if failed to import with * (or from current package) - importsToGenerate.add(ImportPath(fqName, false)) - - val parentFqName = fqName.parent() - - val parentDescriptors = descriptorsByParentFqName[parentFqName]!! - for (descriptor in parentDescriptors.filter { it.importableFqName == fqName }) { - parentDescriptors.remove(descriptor) - } - - if (parentDescriptors.isEmpty()) { // star import is not really needed - importsToGenerate.remove(ImportPath(parentFqName, true)) - } - } - } - - //TODO: drop unused aliases? - aliasImports.mapTo(importsToGenerate) { ImportPath(it.value, false, it.key) } - - val sortedImportsToGenerate = importsToGenerate.sortedWith(importInsertHelper.importSortComparator) - - // check if no changes to imports required - val oldImports = file.importDirectives - if (oldImports.size == sortedImportsToGenerate.size && oldImports.map { it.importPath } == sortedImportsToGenerate) return null - - return sortedImportsToGenerate -} - -private fun buildAliasImportMap(file: KtFile): Map { - val imports = file.importDirectives - val aliasImports = HashMap() - for (import in imports) { - val path = import.importPath ?: continue - val aliasName = path.alias - if (aliasName != null && aliasName != path.fqnPart().shortName() /* we do not keep trivial aliases */) { - aliasImports.put(aliasName, path.fqnPart()) - } - } - return aliasImports -} diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt new file mode 100644 index 00000000000..691e1ae2dbd --- /dev/null +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt @@ -0,0 +1,301 @@ +/* + * Copyright 2010-2016 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.imports + +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.analysis.analyzeInContext +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.resolve.frontendService +import org.jetbrains.kotlin.idea.util.ImportInsertHelper +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.renderer.render +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider +import org.jetbrains.kotlin.resolve.scopes.ImportingScope +import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier +import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf +import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes +import org.jetbrains.kotlin.utils.singletonOrEmptyList +import java.util.* + +class OptimizedImportsBuilder( + private val file: KtFile, + private val data: InputData, + private val options: Options +) { + companion object { + @TestOnly + var testLog: StringBuilder? = null + } + + interface AbstractReference { + val element: KtElement + val dependsOnNames: Collection + fun resolve(bindingContext: BindingContext): Collection + } + + data class InputData( + val descriptorsToImport: Set, + val references: Collection + ) + + data class Options( + val nameCountToUseStarImport: Int, + val nameCountToUseStarImportForMembers: Int, + val isInPackagesToUseStarImport: (FqName) -> Boolean + ) + + private val importInsertHelper = ImportInsertHelper.getInstance(file.project) + + private val lockedImports = HashSet() + + fun buildOptimizedImports(): List? { + // TODO: should we drop unused aliases? + // keep all non-trivial aliases + file.importDirectives + .mapNotNull { it.importPath } + .filterTo(lockedImports) { + val aliasName = it.alias + aliasName != null && aliasName != it.fqnPart().shortName() + } + + while (true) { + val lockedImportsBefore = lockedImports.size + val result = tryBuildOptimizedImports() + if (lockedImports.size == lockedImportsBefore) return result + testLog?.append("Trying to build import list again with locked imports: ${lockedImports.joinToString { it.pathStr }}\n") + } + } + + private fun getExpressionToAnalyze(element: KtElement): KtExpression? { + val parent = element.parent + return when { + parent is KtQualifiedExpression && element == parent.selectorExpression -> parent + parent is KtCallExpression && element == parent.calleeExpression -> getExpressionToAnalyze(parent) + parent is KtUserType -> null //TODO: is it always correct? + else -> element as? KtExpression //TODO: what if not expression? Example: KtPropertyDelegationMethodsReference + } + } + + private fun tryBuildOptimizedImports(): List? { + val importsToGenerate = HashSet() + importsToGenerate.addAll(lockedImports) + + val descriptorsByParentFqName = HashMap>() + for (descriptor in data.descriptorsToImport) { + val fqName = descriptor.importableFqName!! + + val explicitImportPath = ImportPath(fqName, false) + if (explicitImportPath in lockedImports) continue + + if (canUseStarImport(descriptor, fqName)) { + descriptorsByParentFqName.getOrPut(fqName.parent()) { HashSet() }.add(descriptor) + } + else { + importsToGenerate.add(explicitImportPath) + } + } + + val classNamesToCheck = HashSet() + + for (parentFqName in descriptorsByParentFqName.keys) { + val starImportPath = ImportPath(parentFqName, true) + if (starImportPath in lockedImports) continue + + val descriptors = descriptorsByParentFqName[parentFqName]!! + val fqNames = descriptors.map { it.importableFqName!! }.toSet() + val nameCountToUseStar = descriptors.first().nameCountToUseStar() + val useExplicitImports = fqNames.size < nameCountToUseStar && !options.isInPackagesToUseStarImport(parentFqName) + if (useExplicitImports) { + fqNames + .filter { !isImportedByDefault(it) } + .mapTo(importsToGenerate) { ImportPath(it, false) } + } + else { + descriptors + .filterIsInstance() + .mapTo(classNamesToCheck) { it.importableFqName!! } + + if (!fqNames.all(this::isImportedByDefault)) { + importsToGenerate.add(starImportPath) + } + } + } + + // now check that there are no conflicts and all classes are really imported + addExplicitImportsForClassesWhenRequired(classNamesToCheck, descriptorsByParentFqName, importsToGenerate, file) + + val sortedImportsToGenerate = importsToGenerate.sortedWith(importInsertHelper.importSortComparator) + + // check if no changes to imports required + val oldImports = file.importDirectives + if (oldImports.size == sortedImportsToGenerate.size && oldImports.map { it.importPath } == sortedImportsToGenerate) return null + + val originalFileScope = file.getFileResolutionScope() + val newFileScope = buildScopeByImports(file, sortedImportsToGenerate) + + var references = data.references + if (testLog != null) { + // to make log the same for all runs + references = references.sortedBy { it.toString() } + } + for ((names, refs) in references.groupBy { it.dependsOnNames }) { + if (!areScopeSlicesEqual(originalFileScope, newFileScope, names)) { + for (ref in refs) { + val element = ref.element + val bindingContext = element.analyze() + val expressionToAnalyze = getExpressionToAnalyze(element) ?: continue + val newScope = element.getResolutionScope(bindingContext, file.getResolutionFacade()).replaceImportingScopes(newFileScope) + val newBindingContext = expressionToAnalyze.analyzeInContext(newScope, expressionToAnalyze) + + testLog?.append("Additional checking of reference $ref\n") + + val oldTargets = ref.resolve(bindingContext) + val newTargets = ref.resolve(newBindingContext) + if (!areTargetsEqual(oldTargets, newTargets)) { + testLog?.append("Changed resolve of $ref\n") + (oldTargets + newTargets).forEach { lockImportForDescriptor(it) } + } + } + } + } + + return sortedImportsToGenerate + } + + private fun lockImportForDescriptor(descriptor: DeclarationDescriptor) { + val fqName = descriptor.importableFqName ?: return + val explicitImportPath = ImportPath(fqName, false) + val starImportPath = ImportPath(fqName.parent(), true) + if (file.importDirectives.any { it.importPath == explicitImportPath }) { + lockedImports.add(explicitImportPath) + } + else if (file.importDirectives.any { it.importPath == starImportPath }) { + lockedImports.add(starImportPath) + } + } + + private fun addExplicitImportsForClassesWhenRequired( + classNamesToCheck: Collection, + descriptorsByParentFqName: Map>, + importsToGenerate: MutableSet, + originalFile: KtFile + ) { + val scope = buildScopeByImports(originalFile, importsToGenerate.filter { it.isAllUnder }) + for (fqName in classNamesToCheck) { + if (scope.findClassifier(fqName.shortName(), NoLookupLocation.FROM_IDE)?.importableFqName != fqName) { + // add explicit import if failed to import with * (or from current package) + importsToGenerate.add(ImportPath(fqName, false)) + + val parentFqName = fqName.parent() + + val siblingsToImport = descriptorsByParentFqName[parentFqName]!! + for (descriptor in siblingsToImport.filter { it.importableFqName == fqName }) { + siblingsToImport.remove(descriptor) + } + + if (siblingsToImport.isEmpty()) { // star import is not really needed + importsToGenerate.remove(ImportPath(parentFqName, true)) + } + } + } + } + + private fun buildScopeByImports(originalFile: KtFile, importsToGenerate: Collection): ImportingScope { + val fileText = buildString { + append("package ") + append(originalFile.packageFqName.toUnsafe().render()) + append("\n") + + for (importPath in importsToGenerate) { + append("import ") + append(importPath.pathStr) + if (importPath.hasAlias()) { + append("=") + append(importPath.alias!!.render()) + } + append("\n") + } + } + val fileWithImports = KtPsiFactory(originalFile).createAnalyzableFile("Dummy.kt", fileText, originalFile) + return fileWithImports.getFileResolutionScope() + } + + private fun KtFile.getFileResolutionScope() = getResolutionFacade().frontendService().getFileScopes(this).importingScope + + private fun areScopeSlicesEqual(scope1: ImportingScope, scope2: ImportingScope, names: Collection): Boolean { + val tower1 = scope1.extractSliceTower(names) + val tower2 = scope2.extractSliceTower(names) + val iterator1 = tower1.iterator() + val iterator2 = tower2.iterator() + while (true) { + if (!iterator1.hasNext()) { + return !iterator2.hasNext() + } + else if (!iterator2.hasNext()) { + return false + } + else { + if (!areTargetsEqual(iterator1.next(), iterator2.next())) return false + } + } + } + + private fun ImportingScope.extractSliceTower(names: Collection): Sequence> { + return parentsWithSelf + .map { scope -> + names.flatMap { name -> + scope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) + + scope.getContributedVariables(name, NoLookupLocation.FROM_IDE) + + scope.getContributedClassifier(name, NoLookupLocation.FROM_IDE).singletonOrEmptyList() + } + } + .filter { it.isNotEmpty() } + } + + private fun canUseStarImport(descriptor: DeclarationDescriptor, fqName: FqName): Boolean { + return when { + fqName.parent().isRoot -> false + (descriptor.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.OBJECT -> false + else -> true + } + } + + private fun isImportedByDefault(fqName: FqName) = importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file) + + private fun DeclarationDescriptor.nameCountToUseStar(): Int { + val isMember = containingDeclaration is ClassDescriptor + return if (isMember) + options.nameCountToUseStarImportForMembers + else + options.nameCountToUseStarImport + } + + private fun areTargetsEqual(descriptors1: Collection, descriptors2: Collection): Boolean { + return descriptors1.size == descriptors2.size && + descriptors1.zip(descriptors2).all { it.first.importableFqName == it.second.importableFqName } //TODO: can have different order? + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt index 0c2ea582824..6b85d621f76 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt @@ -53,4 +53,6 @@ class KDocReference(element: KDocName): KtMultiReference(element) { } override fun getCanonicalText(): String = element.getNameText() + + override val resolvesByNames: Collection get() = listOf(element.getNameText()) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtArrayAccessReference.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtArrayAccessReference.java index be2c34ecaa1..e760789c39d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtArrayAccessReference.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtArrayAccessReference.java @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.psi.KtArrayAccessExpression; import org.jetbrains.kotlin.psi.KtContainerNode; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; +import org.jetbrains.kotlin.util.OperatorNameConventions; import java.util.ArrayList; import java.util.Collection; @@ -98,4 +99,12 @@ public class KtArrayAccessReference extends KtSimpleReference NAMES = Lists.newArrayList(OperatorNameConventions.GET.getIdentifier(), OperatorNameConventions.SET.getIdentifier()); + + @NotNull + @Override + public Collection getResolvesByNames() { + return NAMES; + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtConstructorDelegationReference.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtConstructorDelegationReference.java index ac1acdaddff..c78e8bb699f 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtConstructorDelegationReference.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtConstructorDelegationReference.java @@ -17,8 +17,12 @@ package org.jetbrains.kotlin.idea.references; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.psi.KtConstructorDelegationReferenceExpression; +import java.util.Collection; +import java.util.Collections; + public class KtConstructorDelegationReference extends KtSimpleReference { public KtConstructorDelegationReference(KtConstructorDelegationReferenceExpression expression) { super(expression); @@ -28,4 +32,10 @@ public class KtConstructorDelegationReference extends KtSimpleReference getResolvesByNames() { + return Collections.emptyList(); + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtDestructuringDeclarationReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtDestructuringDeclarationReference.kt index d99ac119352..b416b3b780f 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtDestructuringDeclarationReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtDestructuringDeclarationReference.kt @@ -22,6 +22,7 @@ import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.utils.singletonOrEmptyList @@ -42,4 +43,10 @@ class KtDestructuringDeclarationReference(element: KtDestructuringDeclarationEnt if (canRename()) return expression throw IncorrectOperationException() } + + override val resolvesByNames: Collection + get() { + val componentIndex = (element.parent as KtDestructuringDeclaration).entries.indexOf(element) + 1 + return listOf("component$componentIndex") + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt index 4bd51c42948..62c84672ffd 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt @@ -20,6 +20,7 @@ import com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.psi.KtForExpression import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.util.OperatorNameConventions class KtForLoopInReference(element: KtForExpression) : KtMultiReference(element) { @@ -35,11 +36,20 @@ class KtForLoopInReference(element: KtForExpression) : KtMultiReference context.get(key, loopRange)?.candidateDescriptor } } + override val resolvesByNames: Collection + get() = NAMES + companion object { private val LOOP_RANGE_KEYS = arrayOf( BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL, BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL ) + + private val NAMES = listOf( + OperatorNameConventions.ITERATOR.identifier, + OperatorNameConventions.NEXT.identifier, + OperatorNameConventions.HAS_NEXT.identifier + ) } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtInvokeFunctionReference.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtInvokeFunctionReference.java index 28c391b0bc2..ebf6dd61ec1 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtInvokeFunctionReference.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtInvokeFunctionReference.java @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.references; +import com.google.common.collect.Lists; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; import com.intellij.psi.MultiRangeReference; @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; +import org.jetbrains.kotlin.util.OperatorNameConventions; import java.util.ArrayList; import java.util.Collection; @@ -112,4 +114,12 @@ public class KtInvokeFunctionReference extends KtSimpleReference NAMES = Lists.newArrayList(OperatorNameConventions.INVOKE.getIdentifier()); + + @NotNull + @Override + public Collection getResolvesByNames() { + return NAMES; + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtPropertyDelegationMethodsReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtPropertyDelegationMethodsReference.kt index a10d2121d82..59ffb3c1e50 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtPropertyDelegationMethodsReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtPropertyDelegationMethodsReference.kt @@ -21,9 +21,9 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtPropertyDelegate -import org.jetbrains.kotlin.resolve.BindingContext -import java.util.Collections import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.util.OperatorNameConventions class KtPropertyDelegationMethodsReference(element: KtPropertyDelegate) : KtMultiReference(element) { @@ -34,17 +34,17 @@ class KtPropertyDelegationMethodsReference(element: KtPropertyDelegate) : KtMult } override fun getTargetDescriptors(context: BindingContext): Collection { - val property = expression.getStrictParentOfType() - if (property == null) { - return Collections.emptyList() - } - val descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property) - if (descriptor !is PropertyDescriptor) { - return Collections.emptyList() - } + val property = expression.getStrictParentOfType() ?: return emptyList() + val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? PropertyDescriptor ?: return emptyList() return (descriptor.accessors.mapNotNull { accessor -> context.get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, accessor)?.candidateDescriptor } + listOfNotNull(context.get(BindingContext.DELEGATED_PROPERTY_PD_RESOLVED_CALL, descriptor)?.candidateDescriptor)) } + + override val resolvesByNames: Collection get() = NAMES + + companion object { + private val NAMES = listOf(OperatorNameConventions.GET_VALUE.identifier, OperatorNameConventions.SET_VALUE.identifier) + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtReference.kt index 27e1b14a691..11cac926d8d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtReference.kt @@ -34,6 +34,8 @@ interface KtReference : PsiPolyVariantReference { fun resolveToDescriptors(bindingContext: BindingContext): Collection override fun getElement(): KtElement + + val resolvesByNames: Collection } abstract class AbstractKtReference(element: T) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtSimpleNameReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtSimpleNameReference.kt index c584efc38db..321ca370ed9 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtSimpleNameReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtSimpleNameReference.kt @@ -204,4 +204,7 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere } override fun getCanonicalText(): String = expression.text + + override val resolvesByNames: Collection + get() = listOf(element.getReferencedName()) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt index ae8b594df4f..0b3210d5a6d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt @@ -74,6 +74,9 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre return renameByPropertyName(newName.identifier) } + override val resolvesByNames: Collection + get() = listOf(element.getReferencedName()) + class Getter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, true) class Setter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, false) } diff --git a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt index aa0539912b9..f88b31c3c4b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt @@ -26,17 +26,16 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings +import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.canBeResolvedViaImport -import org.jetbrains.kotlin.idea.util.ImportInsertHelper +import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.application.runWriteAction -import org.jetbrains.kotlin.idea.util.getFileResolutionScope import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor @@ -45,10 +44,9 @@ import org.jetbrains.kotlin.resolve.scopes.utils.* import java.util.* class KotlinImportOptimizer() : ImportOptimizer { - override fun supports(file: PsiFile?) = file is KtFile - override fun processFile(file: PsiFile?) = Runnable() { + override fun processFile(file: PsiFile?) = Runnable { OptimizeProcess(file as KtFile).execute() } @@ -69,12 +67,13 @@ class KotlinImportOptimizer() : ImportOptimizer { } } - private class CollectUsedDescriptorsVisitor(val file: KtFile) : KtVisitorVoid() { - private val _descriptors = HashSet() + private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() { private val currentPackageName = file.packageFqName + private val descriptorsToImport = HashSet() + private val abstractRefs = ArrayList() - val descriptors: Set - get() = _descriptors + val data: OptimizedImportsBuilder.InputData + get() = OptimizedImportsBuilder.InputData(descriptorsToImport, abstractRefs) override fun visitElement(element: PsiElement) { ProgressIndicatorProvider.checkCanceled() @@ -90,14 +89,16 @@ class KotlinImportOptimizer() : ImportOptimizer { override fun visitKtElement(element: KtElement) { for (reference in element.references) { if (reference !is KtReference) continue + abstractRefs.add(AbstractReferenceImpl(reference)) - val referencedName = (element as? KtNameReferenceExpression)?.getReferencedNameAsName() //TODO: other types of references + val names = reference.resolvesByNames val bindingContext = element.analyze() - //class qualifiers that refer to companion objects should be considered (containing) class references - val targets = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) } - ?: reference.resolveToDescriptors(bindingContext) + val targets = reference.targets(bindingContext) for (target in targets) { + val importableDescriptor = target.getImportableDescriptor() + if (importableDescriptor.name.asString() !in names) continue // resolved via alias + val importableFqName = target.importableFqName ?: continue val parentFqName = importableFqName.parent() if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages @@ -105,13 +106,9 @@ class KotlinImportOptimizer() : ImportOptimizer { if (!reference.canBeResolvedViaImport(target)) continue - val importableDescriptor = target.getImportableDescriptor() - - if (referencedName != null && importableDescriptor.name != referencedName) continue // resolved via alias - if (isAccessibleAsMember(importableDescriptor, element, bindingContext)) continue - _descriptors.add(importableDescriptor) + descriptorsToImport.add(importableDescriptor) } } @@ -145,25 +142,43 @@ class KotlinImportOptimizer() : ImportOptimizer { } return false } + + private class AbstractReferenceImpl(private val reference: KtReference) : OptimizedImportsBuilder.AbstractReference { + override val element: KtElement + get() = reference.element + + override val dependsOnNames: Collection + get() { + val resolvesByNames = reference.resolvesByNames + if (reference is KtInvokeFunctionReference) { + val additionalNames = (reference.element.calleeExpression as? KtNameReferenceExpression)?.mainReference?.resolvesByNames + if (additionalNames != null) { + return (resolvesByNames + additionalNames).map { Name.identifier(it) } + } + } + return resolvesByNames.map { Name.identifier(it) } + } + + override fun resolve(bindingContext: BindingContext) = reference.resolveToDescriptors(bindingContext) + + override fun toString() = reference.toString() + } } companion object { - fun collectDescriptorsToImport(file: KtFile): Set { + fun collectDescriptorsToImport(file: KtFile): OptimizedImportsBuilder.InputData { val visitor = CollectUsedDescriptorsVisitor(file) file.accept(visitor) - return visitor.descriptors + return visitor.data } - fun prepareOptimizedImports(file: KtFile, - descriptorsToImport: Collection): List? { + fun prepareOptimizedImports(file: KtFile, data: OptimizedImportsBuilder.InputData): List? { val settings = KotlinCodeStyleSettings.getInstance(file.project) - return prepareOptimizedImports( - file, - descriptorsToImport, + val options = OptimizedImportsBuilder.Options( settings.NAME_COUNT_TO_USE_STAR_IMPORT, - settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS) { fqName -> - fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS - } + settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS, + isInPackagesToUseStarImport = { fqName -> fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS }) + return OptimizedImportsBuilder(file, data, options).buildOptimizedImports() } fun replaceImports(file: KtFile, imports: List) { @@ -179,5 +194,11 @@ class KotlinImportOptimizer() : ImportOptimizer { import.delete() } } + + private fun KtReference.targets(bindingContext: BindingContext): Collection { + //class qualifiers that refer to companion objects should be considered (containing) class references + return bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) } + ?: resolveToDescriptors(bindingContext) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinUnusedImportInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinUnusedImportInspection.kt index f995fda100d..bc03861da89 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinUnusedImportInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinUnusedImportInspection.kt @@ -41,9 +41,9 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.util.DocumentUtil import com.intellij.util.Processor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer +import org.jetbrains.kotlin.idea.imports.OptimizedImportsBuilder import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtCodeFragment @@ -59,7 +59,7 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() { if (!file.manager.isInProject(file)) return null if (file.importDirectives.isEmpty()) return null - val descriptorsToImport = KotlinImportOptimizer.collectDescriptorsToImport(file) + val data = KotlinImportOptimizer.collectDescriptorsToImport(file) val directives = file.importDirectives val explicitlyImportedFqNames = directives @@ -71,7 +71,7 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() { val fqNames = HashSet() val parentFqNames = HashSet() - for (descriptor in descriptorsToImport) { + for (descriptor in data.descriptorsToImport) { val fqName = descriptor.importableFqName!! fqNames.add(fqName) @@ -119,15 +119,15 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() { } if (isOnTheFly) { - scheduleOptimizeImportsOnTheFly(file, descriptorsToImport) + scheduleOptimizeImportsOnTheFly(file, data) } return problems.toTypedArray() } - private fun scheduleOptimizeImportsOnTheFly(file: KtFile, descriptorsToImport: Set) { + private fun scheduleOptimizeImportsOnTheFly(file: KtFile, data: OptimizedImportsBuilder.InputData) { if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return - val optimizedImports = KotlinImportOptimizer.prepareOptimizedImports(file, descriptorsToImport) ?: return // return if already optimized + val optimizedImports = KotlinImportOptimizer.prepareOptimizedImports(file, data) ?: return // return if already optimized // unwrap progress indicator val progress = generateSequence(ProgressManager.getInstance().progressIndicator) { diff --git a/idea/testData/editor/optimizeImports/Enums.kt.log b/idea/testData/editor/optimizeImports/Enums.kt.log new file mode 100644 index 00000000000..12af1651190 --- /dev/null +++ b/idea/testData/editor/optimizeImports/Enums.kt.log @@ -0,0 +1,6 @@ +Additional checking of reference Getter: E2 +Additional checking of reference KtSimpleNameReference: E2 +Additional checking of reference Getter: E22 +Additional checking of reference KtSimpleNameReference: E22 +Additional checking of reference Getter: E31 +Additional checking of reference KtSimpleNameReference: E31 diff --git a/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt.log b/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt.log new file mode 100644 index 00000000000..2d7098d58c0 --- /dev/null +++ b/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt.log @@ -0,0 +1,3 @@ +Additional checking of reference Getter: run +Additional checking of reference KtSimpleNameReference: run +Additional checking of reference KtInvokeFunctionReference: run("") diff --git a/idea/testData/editor/optimizeImports/KT11640.dependency.kt b/idea/testData/editor/optimizeImports/KT11640.dependency.kt new file mode 100644 index 00000000000..8a3a3c6c8b0 --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640.dependency.kt @@ -0,0 +1,6 @@ +package bug.a + +class A(val foo: MyFunction) + +class MyFunction +operator fun MyFunction.invoke() = println("invoke convention") diff --git a/idea/testData/editor/optimizeImports/KT11640.kt b/idea/testData/editor/optimizeImports/KT11640.kt new file mode 100644 index 00000000000..24e5e66076a --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640.kt @@ -0,0 +1,13 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 2 +package bug.b + +import bug.a.* +import bug.a.invoke + +fun A.foo() = println("extension function") + +fun main(args: Array) { + val a = A(MyFunction()) + + a.foo() +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/KT11640.kt.after b/idea/testData/editor/optimizeImports/KT11640.kt.after new file mode 100644 index 00000000000..24e5e66076a --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640.kt.after @@ -0,0 +1,13 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 2 +package bug.b + +import bug.a.* +import bug.a.invoke + +fun A.foo() = println("extension function") + +fun main(args: Array) { + val a = A(MyFunction()) + + a.foo() +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/KT11640.kt.log b/idea/testData/editor/optimizeImports/KT11640.kt.log new file mode 100644 index 00000000000..b5c5f921078 --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640.kt.log @@ -0,0 +1,6 @@ +Additional checking of reference KtInvokeFunctionReference: A(MyFunction()) +Additional checking of reference KtInvokeFunctionReference: MyFunction() +Additional checking of reference KtInvokeFunctionReference: foo() +Changed resolve of KtInvokeFunctionReference: foo() +Additional checking of reference KtInvokeFunctionReference: println("extension function") +Trying to build import list again with locked imports: bug.a.invoke \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/KT11640_1.dependency.kt b/idea/testData/editor/optimizeImports/KT11640_1.dependency.kt new file mode 100644 index 00000000000..8a3a3c6c8b0 --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640_1.dependency.kt @@ -0,0 +1,6 @@ +package bug.a + +class A(val foo: MyFunction) + +class MyFunction +operator fun MyFunction.invoke() = println("invoke convention") diff --git a/idea/testData/editor/optimizeImports/KT11640_1.kt b/idea/testData/editor/optimizeImports/KT11640_1.kt new file mode 100644 index 00000000000..90c6582f777 --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640_1.kt @@ -0,0 +1,15 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 10 +package bug.b + +import bug.a.* + +fun A.foo() = println("extension function") + +fun main(args: Array) { + val func = MyFunction() + func() + + val a = A(func) + + a.foo() +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/KT11640_1.kt.after b/idea/testData/editor/optimizeImports/KT11640_1.kt.after new file mode 100644 index 00000000000..90c6582f777 --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640_1.kt.after @@ -0,0 +1,15 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 10 +package bug.b + +import bug.a.* + +fun A.foo() = println("extension function") + +fun main(args: Array) { + val func = MyFunction() + func() + + val a = A(func) + + a.foo() +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/KT11640_1.kt.log b/idea/testData/editor/optimizeImports/KT11640_1.kt.log new file mode 100644 index 00000000000..e837958e9dc --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT11640_1.kt.log @@ -0,0 +1,3 @@ +Additional checking of reference KtInvokeFunctionReference: foo() +Changed resolve of KtInvokeFunctionReference: foo() +Trying to build import list again with locked imports: bug.a.* \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/KT9875.kt.log b/idea/testData/editor/optimizeImports/KT9875.kt.log new file mode 100644 index 00000000000..077976928cc --- /dev/null +++ b/idea/testData/editor/optimizeImports/KT9875.kt.log @@ -0,0 +1,3 @@ +Additional checking of reference Getter: Inner2 +Additional checking of reference KtSimpleNameReference: Inner2 +Additional checking of reference KtInvokeFunctionReference: Inner2() diff --git a/idea/testData/editor/optimizeImports/Overloads.dependency1.kt b/idea/testData/editor/optimizeImports/Overloads.dependency1.kt new file mode 100644 index 00000000000..61fca4599a8 --- /dev/null +++ b/idea/testData/editor/optimizeImports/Overloads.dependency1.kt @@ -0,0 +1,7 @@ +package pack1 + +fun foo(o: Any){} + +val u1 = 1 +val u2 = 1 +val u3 = 1 diff --git a/idea/testData/editor/optimizeImports/Overloads.dependency2.kt b/idea/testData/editor/optimizeImports/Overloads.dependency2.kt new file mode 100644 index 00000000000..3dedf933ebd --- /dev/null +++ b/idea/testData/editor/optimizeImports/Overloads.dependency2.kt @@ -0,0 +1,7 @@ +package pack2 + +fun foo(o: String){} + +val v1 = 1 +val v2 = 1 +val v3 = 1 \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/Overloads.kt b/idea/testData/editor/optimizeImports/Overloads.kt new file mode 100644 index 00000000000..a2c89777638 --- /dev/null +++ b/idea/testData/editor/optimizeImports/Overloads.kt @@ -0,0 +1,15 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 3 + +import pack1.foo +import pack1.u1 +import pack1.u2 +import pack1.u3 +import pack1.u4 +import pack1.u5 +import pack2.* + +fun f() { + foo("") + v1 + v2 + v3 + u1 + u2 + u3 +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/Overloads.kt.after b/idea/testData/editor/optimizeImports/Overloads.kt.after new file mode 100644 index 00000000000..1d3c4acff4f --- /dev/null +++ b/idea/testData/editor/optimizeImports/Overloads.kt.after @@ -0,0 +1,11 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 3 + +import pack1.* +import pack1.foo +import pack2.* + +fun f() { + foo("") + v1 + v2 + v3 + u1 + u2 + u3 +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/Overloads.kt.log b/idea/testData/editor/optimizeImports/Overloads.kt.log new file mode 100644 index 00000000000..9199f926c31 --- /dev/null +++ b/idea/testData/editor/optimizeImports/Overloads.kt.log @@ -0,0 +1,8 @@ +Additional checking of reference Getter: foo +Additional checking of reference KtSimpleNameReference: foo +Changed resolve of KtSimpleNameReference: foo +Additional checking of reference KtInvokeFunctionReference: foo("") +Trying to build import list again with locked imports: pack1.foo, pack2.* +Additional checking of reference Getter: foo +Additional checking of reference KtSimpleNameReference: foo +Additional checking of reference KtInvokeFunctionReference: foo("") diff --git a/idea/testData/editor/optimizeImports/TwoConstructors.dependency1.kt b/idea/testData/editor/optimizeImports/TwoConstructors.dependency1.kt new file mode 100644 index 00000000000..5ecf487edda --- /dev/null +++ b/idea/testData/editor/optimizeImports/TwoConstructors.dependency1.kt @@ -0,0 +1,3 @@ +package p1 + +class A(p: Int) \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/TwoConstructors.dependency2.kt b/idea/testData/editor/optimizeImports/TwoConstructors.dependency2.kt new file mode 100644 index 00000000000..5b80d52a5e4 --- /dev/null +++ b/idea/testData/editor/optimizeImports/TwoConstructors.dependency2.kt @@ -0,0 +1,3 @@ +package p2 + +class A(s: String) \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/TwoConstructors.kt b/idea/testData/editor/optimizeImports/TwoConstructors.kt new file mode 100644 index 00000000000..7c87269930e --- /dev/null +++ b/idea/testData/editor/optimizeImports/TwoConstructors.kt @@ -0,0 +1,8 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 2 +import p1.* +import p2.A + +fun f() { + A(1) + A("") +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/TwoConstructors.kt.after b/idea/testData/editor/optimizeImports/TwoConstructors.kt.after new file mode 100644 index 00000000000..7c87269930e --- /dev/null +++ b/idea/testData/editor/optimizeImports/TwoConstructors.kt.after @@ -0,0 +1,8 @@ +// NAME_COUNT_TO_USE_STAR_IMPORT: 2 +import p1.* +import p2.A + +fun f() { + A(1) + A("") +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/TwoConstructors.kt.log b/idea/testData/editor/optimizeImports/TwoConstructors.kt.log new file mode 100644 index 00000000000..998274a6599 --- /dev/null +++ b/idea/testData/editor/optimizeImports/TwoConstructors.kt.log @@ -0,0 +1,9 @@ +Additional checking of reference Getter: A +Additional checking of reference Getter: A +Additional checking of reference KtSimpleNameReference: A +Changed resolve of KtSimpleNameReference: A +Additional checking of reference KtSimpleNameReference: A +Changed resolve of KtSimpleNameReference: A +Additional checking of reference KtInvokeFunctionReference: A("") +Additional checking of reference KtInvokeFunctionReference: A(1) +Trying to build import list again with locked imports: p2.A, p1.* diff --git a/idea/tests/org/jetbrains/kotlin/AbstractImportsTest.kt b/idea/tests/org/jetbrains/kotlin/AbstractImportsTest.kt index 7c854e85c71..49341c93361 100644 --- a/idea/tests/org/jetbrains/kotlin/AbstractImportsTest.kt +++ b/idea/tests/org/jetbrains/kotlin/AbstractImportsTest.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin import com.intellij.psi.codeStyle.CodeStyleSettingsManager import com.intellij.psi.codeStyle.PackageEntry +import junit.framework.TestCase import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor @@ -40,13 +41,12 @@ abstract class AbstractImportsTest : KotlinLightCodeInsightFixtureTestCase() { try { val fixture = myFixture - val dependencyPath = testPath.replace(".kt", ".dependency.kt") - if (File(dependencyPath).exists()) { - fixture.configureByFile(dependencyPath) - } - val javaDependencyPath = testPath.replace(".kt", ".dependency.java") - if (File(javaDependencyPath).exists()) { - fixture.configureByFile(javaDependencyPath) + val dependencySuffixes = listOf(".dependency.kt", ".dependency.java", ".dependency1.kt", ".dependency2.kt") + for (suffix in dependencySuffixes) { + val dependencyPath = testPath.replace(".kt", suffix) + if (File(dependencyPath).exists()) { + fixture.configureByFile(dependencyPath) + } } fixture.configureByFile(testPath) @@ -66,18 +66,26 @@ abstract class AbstractImportsTest : KotlinLightCodeInsightFixtureTestCase() { codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), true)) } - project.executeWriteCommand("") { - doTest(file) - } + val log = project.executeWriteCommand("") { doTest(file) } KotlinTestUtils.assertEqualsToFile(File(testPath + ".after"), myFixture.file.text) + if (log != null) { + val logFile = File(testPath + ".log") + if (log.isNotEmpty()) { + KotlinTestUtils.assertEqualsToFile(logFile, log) + } + else { + TestCase.assertFalse(logFile.exists()) + } + } } finally { settingManager.dropTemporarySettings() } } - protected abstract fun doTest(file: KtFile) + // returns test log + protected abstract fun doTest(file: KtFile): String? protected open val nameCountToUseStarImportDefault: Int get() = 1 diff --git a/idea/tests/org/jetbrains/kotlin/addImport/AbstractAddImportTest.kt b/idea/tests/org/jetbrains/kotlin/addImport/AbstractAddImportTest.kt index 148cc84bdf2..e6013dab577 100644 --- a/idea/tests/org/jetbrains/kotlin/addImport/AbstractAddImportTest.kt +++ b/idea/tests/org/jetbrains/kotlin/addImport/AbstractAddImportTest.kt @@ -29,7 +29,7 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.test.InTextDirectivesUtils abstract class AbstractAddImportTest : AbstractImportsTest() { - override fun doTest(file: KtFile) { + override fun doTest(file: KtFile): String? { var descriptorName = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// IMPORT:") ?: error("No IMPORT directive defined") @@ -57,5 +57,7 @@ abstract class AbstractAddImportTest : AbstractImportsTest() { } } } + + return null } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/imports/AbstractOptimizeImportsTest.kt b/idea/tests/org/jetbrains/kotlin/idea/imports/AbstractOptimizeImportsTest.kt index 76155edac6e..1b0d726c993 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/imports/AbstractOptimizeImportsTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/imports/AbstractOptimizeImportsTest.kt @@ -16,17 +16,19 @@ package org.jetbrains.kotlin.idea.imports -import com.intellij.openapi.command.CommandProcessor -import com.intellij.openapi.command.UndoConfirmationPolicy -import java.io.File -import org.junit.Assert -import org.jetbrains.kotlin.idea.test.PluginTestCaseBase -import org.jetbrains.kotlin.* -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.AbstractImportsTest +import org.jetbrains.kotlin.psi.KtFile abstract class AbstractOptimizeImportsTest() : AbstractImportsTest() { - override fun doTest(file: KtFile) { - KotlinImportOptimizer().processFile(file).run() + override fun doTest(file: KtFile): String { + OptimizedImportsBuilder.testLog = StringBuilder() + try { + KotlinImportOptimizer().processFile(file).run() + return OptimizedImportsBuilder.testLog.toString() + } + finally { + OptimizedImportsBuilder.testLog = null + } } override val nameCountToUseStarImportDefault: Int diff --git a/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java index c5a5aa0f2ef..3fe0a83db98 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java @@ -155,6 +155,18 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { doTest(fileName); } + @TestMetadata("KT11640.kt") + public void testKT11640() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/KT11640.kt"); + doTest(fileName); + } + + @TestMetadata("KT11640_1.kt") + public void testKT11640_1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/KT11640_1.kt"); + doTest(fileName); + } + @TestMetadata("KT13766.kt") public void testKT13766() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/KT13766.kt"); @@ -215,6 +227,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { doTest(fileName); } + @TestMetadata("Overloads.kt") + public void testOverloads() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/Overloads.kt"); + doTest(fileName); + } + @TestMetadata("RemoveImportsIfGeneral.kt") public void testRemoveImportsIfGeneral() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/RemoveImportsIfGeneral.kt"); @@ -251,6 +269,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { doTest(fileName); } + @TestMetadata("TwoConstructors.kt") + public void testTwoConstructors() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/TwoConstructors.kt"); + doTest(fileName); + } + @TestMetadata("TypeAliasUsage.kt") public void testTypeAliasUsage() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/TypeAliasUsage.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/shortenRefs/AbstractShortenRefsTest.kt b/idea/tests/org/jetbrains/kotlin/shortenRefs/AbstractShortenRefsTest.kt index b4c543c5634..c33bb4edde4 100644 --- a/idea/tests/org/jetbrains/kotlin/shortenRefs/AbstractShortenRefsTest.kt +++ b/idea/tests/org/jetbrains/kotlin/shortenRefs/AbstractShortenRefsTest.kt @@ -16,17 +16,18 @@ package org.jetbrains.kotlin.shortenRefs -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.AbstractImportsTest +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.psi.KtFile abstract class AbstractShortenRefsTest : AbstractImportsTest() { - override fun doTest(file: KtFile) { + override fun doTest(file: KtFile): String? { val selectionModel = myFixture.editor.selectionModel if (!selectionModel.hasSelection()) error("No selection in input file") ShortenReferences { ShortenReferences.Options(removeThis = true, removeThisLabels = true) } .process(file, selectionModel.selectionStart, selectionModel.selectionEnd) selectionModel.removeSelection() + return null } override val nameCountToUseStarImportDefault: Int