Optimize imports to prevent accedential changes in resolve because of import priorities

#KT-11640 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-09-15 15:30:56 +03:00
parent 7628fa1608
commit 95a3a29382
40 changed files with 651 additions and 191 deletions
@@ -19,23 +19,14 @@
package org.jetbrains.kotlin.idea.imports package org.jetbrains.kotlin.idea.imports
import org.jetbrains.kotlin.descriptors.* 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.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.psi.KtReferenceExpression
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import java.util.* import java.util.*
@@ -97,117 +88,3 @@ fun KtReferenceExpression.getImportableTargets(bindingContext: BindingContext):
return targets.map { it.getImportableDescriptor() }.toSet() return targets.map { it.getImportableDescriptor() }.toSet()
} }
fun prepareOptimizedImports(
file: KtFile,
descriptorsToImport: Collection<DeclarationDescriptor>,
nameCountToUseStarImport: Int,
nameCountToUseStarImportForMembers: Int,
isInPackagesToUseStarImport: (FqName) -> Boolean
): List<ImportPath>? {
val importInsertHelper = ImportInsertHelper.getInstance(file.project)
val aliasImports = buildAliasImportMap(file)
val importsToGenerate = HashSet<ImportPath>()
val descriptorsByParentFqName = hashMapOf<FqName, MutableSet<DeclarationDescriptor>>()
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<FqName>()
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<Name, FqName> {
val imports = file.importDirectives
val aliasImports = HashMap<Name, FqName>()
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
}
@@ -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<Name>
fun resolve(bindingContext: BindingContext): Collection<DeclarationDescriptor>
}
data class InputData(
val descriptorsToImport: Set<DeclarationDescriptor>,
val references: Collection<AbstractReference>
)
data class Options(
val nameCountToUseStarImport: Int,
val nameCountToUseStarImportForMembers: Int,
val isInPackagesToUseStarImport: (FqName) -> Boolean
)
private val importInsertHelper = ImportInsertHelper.getInstance(file.project)
private val lockedImports = HashSet<ImportPath>()
fun buildOptimizedImports(): List<ImportPath>? {
// 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<ImportPath>? {
val importsToGenerate = HashSet<ImportPath>()
importsToGenerate.addAll(lockedImports)
val descriptorsByParentFqName = HashMap<FqName, MutableSet<DeclarationDescriptor>>()
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<FqName>()
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<ClassDescriptor>()
.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<FqName>,
descriptorsByParentFqName: Map<FqName, MutableSet<DeclarationDescriptor>>,
importsToGenerate: MutableSet<ImportPath>,
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<ImportPath>): 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<FileScopeProvider>().getFileScopes(this).importingScope
private fun areScopeSlicesEqual(scope1: ImportingScope, scope2: ImportingScope, names: Collection<Name>): 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<Name>): Sequence<Collection<DeclarationDescriptor>> {
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<DeclarationDescriptor>, descriptors2: Collection<DeclarationDescriptor>): Boolean {
return descriptors1.size == descriptors2.size &&
descriptors1.zip(descriptors2).all { it.first.importableFqName == it.second.importableFqName } //TODO: can have different order?
}
}
@@ -53,4 +53,6 @@ class KDocReference(element: KDocName): KtMultiReference<KDocName>(element) {
} }
override fun getCanonicalText(): String = element.getNameText() override fun getCanonicalText(): String = element.getNameText()
override val resolvesByNames: Collection<String> get() = listOf(element.getNameText())
} }
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.psi.KtArrayAccessExpression;
import org.jetbrains.kotlin.psi.KtContainerNode; import org.jetbrains.kotlin.psi.KtContainerNode;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -98,4 +99,12 @@ public class KtArrayAccessReference extends KtSimpleReference<KtArrayAccessExpre
public PsiElement handleElementRename(@Nullable String newElementName) { public PsiElement handleElementRename(@Nullable String newElementName) {
return ReferenceUtilKt.renameImplicitConventionalCall(this, newElementName); return ReferenceUtilKt.renameImplicitConventionalCall(this, newElementName);
} }
private static final List<String> NAMES = Lists.newArrayList(OperatorNameConventions.GET.getIdentifier(), OperatorNameConventions.SET.getIdentifier());
@NotNull
@Override
public Collection<String> getResolvesByNames() {
return NAMES;
}
} }
@@ -17,8 +17,12 @@
package org.jetbrains.kotlin.idea.references; package org.jetbrains.kotlin.idea.references;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.KtConstructorDelegationReferenceExpression; import org.jetbrains.kotlin.psi.KtConstructorDelegationReferenceExpression;
import java.util.Collection;
import java.util.Collections;
public class KtConstructorDelegationReference extends KtSimpleReference<KtConstructorDelegationReferenceExpression> { public class KtConstructorDelegationReference extends KtSimpleReference<KtConstructorDelegationReferenceExpression> {
public KtConstructorDelegationReference(KtConstructorDelegationReferenceExpression expression) { public KtConstructorDelegationReference(KtConstructorDelegationReferenceExpression expression) {
super(expression); super(expression);
@@ -28,4 +32,10 @@ public class KtConstructorDelegationReference extends KtSimpleReference<KtConstr
public TextRange getRangeInElement() { public TextRange getRangeInElement() {
return new TextRange(0, getElement().getTextLength()); return new TextRange(0, getElement().getTextLength());
} }
@NotNull
@Override
public Collection<String> getResolvesByNames() {
return Collections.emptyList();
}
} }
@@ -22,6 +22,7 @@ import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.utils.singletonOrEmptyList import org.jetbrains.kotlin.utils.singletonOrEmptyList
@@ -42,4 +43,10 @@ class KtDestructuringDeclarationReference(element: KtDestructuringDeclarationEnt
if (canRename()) return expression if (canRename()) return expression
throw IncorrectOperationException() throw IncorrectOperationException()
} }
override val resolvesByNames: Collection<String>
get() {
val componentIndex = (element.parent as KtDestructuringDeclaration).entries.indexOf(element) + 1
return listOf("component$componentIndex")
}
} }
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.KtForExpression import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.util.OperatorNameConventions
class KtForLoopInReference(element: KtForExpression) : KtMultiReference<KtForExpression>(element) { class KtForLoopInReference(element: KtForExpression) : KtMultiReference<KtForExpression>(element) {
@@ -35,11 +36,20 @@ class KtForLoopInReference(element: KtForExpression) : KtMultiReference<KtForExp
return LOOP_RANGE_KEYS.mapNotNull { key -> context.get(key, loopRange)?.candidateDescriptor } return LOOP_RANGE_KEYS.mapNotNull { key -> context.get(key, loopRange)?.candidateDescriptor }
} }
override val resolvesByNames: Collection<String>
get() = NAMES
companion object { companion object {
private val LOOP_RANGE_KEYS = arrayOf( private val LOOP_RANGE_KEYS = arrayOf(
BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL, BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL,
BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL,
BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL
) )
private val NAMES = listOf(
OperatorNameConventions.ITERATOR.identifier,
OperatorNameConventions.NEXT.identifier,
OperatorNameConventions.HAS_NEXT.identifier
)
} }
} }
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.idea.references; package org.jetbrains.kotlin.idea.references;
import com.google.common.collect.Lists;
import com.intellij.lang.ASTNode; import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.psi.MultiRangeReference; 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.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -112,4 +114,12 @@ public class KtInvokeFunctionReference extends KtSimpleReference<KtCallExpressio
public PsiElement handleElementRename(@Nullable String newElementName) { public PsiElement handleElementRename(@Nullable String newElementName) {
return ReferenceUtilKt.renameImplicitConventionalCall(this, newElementName); return ReferenceUtilKt.renameImplicitConventionalCall(this, newElementName);
} }
private static final List<String> NAMES = Lists.newArrayList(OperatorNameConventions.INVOKE.getIdentifier());
@NotNull
@Override
public Collection<String> getResolvesByNames() {
return NAMES;
}
} }
@@ -21,9 +21,9 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyDelegate 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.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.util.OperatorNameConventions
class KtPropertyDelegationMethodsReference(element: KtPropertyDelegate) : KtMultiReference<KtPropertyDelegate>(element) { class KtPropertyDelegationMethodsReference(element: KtPropertyDelegate) : KtMultiReference<KtPropertyDelegate>(element) {
@@ -34,17 +34,17 @@ class KtPropertyDelegationMethodsReference(element: KtPropertyDelegate) : KtMult
} }
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> { override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
val property = expression.getStrictParentOfType<KtProperty>() val property = expression.getStrictParentOfType<KtProperty>() ?: return emptyList()
if (property == null) { val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? PropertyDescriptor ?: return emptyList()
return Collections.emptyList()
}
val descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property)
if (descriptor !is PropertyDescriptor) {
return Collections.emptyList()
}
return (descriptor.accessors.mapNotNull { return (descriptor.accessors.mapNotNull {
accessor -> accessor ->
context.get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, accessor)?.candidateDescriptor context.get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, accessor)?.candidateDescriptor
} + listOfNotNull(context.get(BindingContext.DELEGATED_PROPERTY_PD_RESOLVED_CALL, descriptor)?.candidateDescriptor)) } + listOfNotNull(context.get(BindingContext.DELEGATED_PROPERTY_PD_RESOLVED_CALL, descriptor)?.candidateDescriptor))
} }
override val resolvesByNames: Collection<String> get() = NAMES
companion object {
private val NAMES = listOf(OperatorNameConventions.GET_VALUE.identifier, OperatorNameConventions.SET_VALUE.identifier)
}
} }
@@ -34,6 +34,8 @@ interface KtReference : PsiPolyVariantReference {
fun resolveToDescriptors(bindingContext: BindingContext): Collection<DeclarationDescriptor> fun resolveToDescriptors(bindingContext: BindingContext): Collection<DeclarationDescriptor>
override fun getElement(): KtElement override fun getElement(): KtElement
val resolvesByNames: Collection<String>
} }
abstract class AbstractKtReference<T : KtElement>(element: T) abstract class AbstractKtReference<T : KtElement>(element: T)
@@ -204,4 +204,7 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
} }
override fun getCanonicalText(): String = expression.text override fun getCanonicalText(): String = expression.text
override val resolvesByNames: Collection<String>
get() = listOf(element.getReferencedName())
} }
@@ -74,6 +74,9 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre
return renameByPropertyName(newName.identifier) return renameByPropertyName(newName.identifier)
} }
override val resolvesByNames: Collection<String>
get() = listOf(element.getReferencedName())
class Getter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, true) class Getter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, true)
class Setter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, false) class Setter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, false)
} }
@@ -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.getNullableModuleInfo
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings 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.KtReference
import org.jetbrains.kotlin.idea.references.canBeResolvedViaImport 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.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getFileResolutionScope
import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
@@ -45,10 +44,9 @@ import org.jetbrains.kotlin.resolve.scopes.utils.*
import java.util.* import java.util.*
class KotlinImportOptimizer() : ImportOptimizer { class KotlinImportOptimizer() : ImportOptimizer {
override fun supports(file: PsiFile?) = file is KtFile 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() OptimizeProcess(file as KtFile).execute()
} }
@@ -69,12 +67,13 @@ class KotlinImportOptimizer() : ImportOptimizer {
} }
} }
private class CollectUsedDescriptorsVisitor(val file: KtFile) : KtVisitorVoid() { private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() {
private val _descriptors = HashSet<DeclarationDescriptor>()
private val currentPackageName = file.packageFqName private val currentPackageName = file.packageFqName
private val descriptorsToImport = HashSet<DeclarationDescriptor>()
private val abstractRefs = ArrayList<OptimizedImportsBuilder.AbstractReference>()
val descriptors: Set<DeclarationDescriptor> val data: OptimizedImportsBuilder.InputData
get() = _descriptors get() = OptimizedImportsBuilder.InputData(descriptorsToImport, abstractRefs)
override fun visitElement(element: PsiElement) { override fun visitElement(element: PsiElement) {
ProgressIndicatorProvider.checkCanceled() ProgressIndicatorProvider.checkCanceled()
@@ -90,14 +89,16 @@ class KotlinImportOptimizer() : ImportOptimizer {
override fun visitKtElement(element: KtElement) { override fun visitKtElement(element: KtElement) {
for (reference in element.references) { for (reference in element.references) {
if (reference !is KtReference) continue 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() val bindingContext = element.analyze()
//class qualifiers that refer to companion objects should be considered (containing) class references val targets = reference.targets(bindingContext)
val targets = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) }
?: reference.resolveToDescriptors(bindingContext)
for (target in targets) { for (target in targets) {
val importableDescriptor = target.getImportableDescriptor()
if (importableDescriptor.name.asString() !in names) continue // resolved via alias
val importableFqName = target.importableFqName ?: continue val importableFqName = target.importableFqName ?: continue
val parentFqName = importableFqName.parent() val parentFqName = importableFqName.parent()
if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages 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 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 if (isAccessibleAsMember(importableDescriptor, element, bindingContext)) continue
_descriptors.add(importableDescriptor) descriptorsToImport.add(importableDescriptor)
} }
} }
@@ -145,25 +142,43 @@ class KotlinImportOptimizer() : ImportOptimizer {
} }
return false return false
} }
private class AbstractReferenceImpl(private val reference: KtReference) : OptimizedImportsBuilder.AbstractReference {
override val element: KtElement
get() = reference.element
override val dependsOnNames: Collection<Name>
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 { companion object {
fun collectDescriptorsToImport(file: KtFile): Set<DeclarationDescriptor> { fun collectDescriptorsToImport(file: KtFile): OptimizedImportsBuilder.InputData {
val visitor = CollectUsedDescriptorsVisitor(file) val visitor = CollectUsedDescriptorsVisitor(file)
file.accept(visitor) file.accept(visitor)
return visitor.descriptors return visitor.data
} }
fun prepareOptimizedImports(file: KtFile, fun prepareOptimizedImports(file: KtFile, data: OptimizedImportsBuilder.InputData): List<ImportPath>? {
descriptorsToImport: Collection<DeclarationDescriptor>): List<ImportPath>? {
val settings = KotlinCodeStyleSettings.getInstance(file.project) val settings = KotlinCodeStyleSettings.getInstance(file.project)
return prepareOptimizedImports( val options = OptimizedImportsBuilder.Options(
file,
descriptorsToImport,
settings.NAME_COUNT_TO_USE_STAR_IMPORT, settings.NAME_COUNT_TO_USE_STAR_IMPORT,
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS) { fqName -> settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS,
fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS isInPackagesToUseStarImport = { fqName -> fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS })
} return OptimizedImportsBuilder(file, data, options).buildOptimizedImports()
} }
fun replaceImports(file: KtFile, imports: List<ImportPath>) { fun replaceImports(file: KtFile, imports: List<ImportPath>) {
@@ -179,5 +194,11 @@ class KotlinImportOptimizer() : ImportOptimizer {
import.delete() import.delete()
} }
} }
private fun KtReference.targets(bindingContext: BindingContext): Collection<DeclarationDescriptor> {
//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)
}
} }
} }
@@ -41,9 +41,9 @@ import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.util.DocumentUtil import com.intellij.util.DocumentUtil
import com.intellij.util.Processor import com.intellij.util.Processor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer 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.idea.imports.importableFqName
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtCodeFragment
@@ -59,7 +59,7 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
if (!file.manager.isInProject(file)) return null if (!file.manager.isInProject(file)) return null
if (file.importDirectives.isEmpty()) return null if (file.importDirectives.isEmpty()) return null
val descriptorsToImport = KotlinImportOptimizer.collectDescriptorsToImport(file) val data = KotlinImportOptimizer.collectDescriptorsToImport(file)
val directives = file.importDirectives val directives = file.importDirectives
val explicitlyImportedFqNames = directives val explicitlyImportedFqNames = directives
@@ -71,7 +71,7 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
val fqNames = HashSet<FqName>() val fqNames = HashSet<FqName>()
val parentFqNames = HashSet<FqName>() val parentFqNames = HashSet<FqName>()
for (descriptor in descriptorsToImport) { for (descriptor in data.descriptorsToImport) {
val fqName = descriptor.importableFqName!! val fqName = descriptor.importableFqName!!
fqNames.add(fqName) fqNames.add(fqName)
@@ -119,15 +119,15 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
} }
if (isOnTheFly) { if (isOnTheFly) {
scheduleOptimizeImportsOnTheFly(file, descriptorsToImport) scheduleOptimizeImportsOnTheFly(file, data)
} }
return problems.toTypedArray() return problems.toTypedArray()
} }
private fun scheduleOptimizeImportsOnTheFly(file: KtFile, descriptorsToImport: Set<DeclarationDescriptor>) { private fun scheduleOptimizeImportsOnTheFly(file: KtFile, data: OptimizedImportsBuilder.InputData) {
if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return 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 // unwrap progress indicator
val progress = generateSequence(ProgressManager.getInstance().progressIndicator) { val progress = generateSequence(ProgressManager.getInstance().progressIndicator) {
+6
View File
@@ -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
@@ -0,0 +1,3 @@
Additional checking of reference Getter: run
Additional checking of reference KtSimpleNameReference: run
Additional checking of reference KtInvokeFunctionReference: run("")
@@ -0,0 +1,6 @@
package bug.a
class A(val foo: MyFunction)
class MyFunction
operator fun MyFunction.invoke() = println("invoke convention")
+13
View File
@@ -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<String>) {
val a = A(MyFunction())
a.foo()
}
+13
View File
@@ -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<String>) {
val a = A(MyFunction())
a.foo()
}
+6
View File
@@ -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
@@ -0,0 +1,6 @@
package bug.a
class A(val foo: MyFunction)
class MyFunction
operator fun MyFunction.invoke() = println("invoke convention")
+15
View File
@@ -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<String>) {
val func = MyFunction()
func()
val a = A(func)
a.foo()
}
+15
View File
@@ -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<String>) {
val func = MyFunction()
func()
val a = A(func)
a.foo()
}
+3
View File
@@ -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.*
+3
View File
@@ -0,0 +1,3 @@
Additional checking of reference Getter: Inner2
Additional checking of reference KtSimpleNameReference: Inner2
Additional checking of reference KtInvokeFunctionReference: Inner2()
@@ -0,0 +1,7 @@
package pack1
fun foo(o: Any){}
val u1 = 1
val u2 = 1
val u3 = 1
@@ -0,0 +1,7 @@
package pack2
fun foo(o: String){}
val v1 = 1
val v2 = 1
val v3 = 1
+15
View File
@@ -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
}
+11
View File
@@ -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
}
+8
View File
@@ -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("")
@@ -0,0 +1,3 @@
package p1
class A(p: Int)
@@ -0,0 +1,3 @@
package p2
class A(s: String)
@@ -0,0 +1,8 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2
import p1.*
import p2.A
fun f() {
A(1)
A("")
}
@@ -0,0 +1,8 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2
import p1.*
import p2.A
fun f() {
A(1)
A("")
}
@@ -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.*
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin
import com.intellij.psi.codeStyle.CodeStyleSettingsManager import com.intellij.psi.codeStyle.CodeStyleSettingsManager
import com.intellij.psi.codeStyle.PackageEntry import com.intellij.psi.codeStyle.PackageEntry
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
@@ -40,13 +41,12 @@ abstract class AbstractImportsTest : KotlinLightCodeInsightFixtureTestCase() {
try { try {
val fixture = myFixture val fixture = myFixture
val dependencyPath = testPath.replace(".kt", ".dependency.kt") val dependencySuffixes = listOf(".dependency.kt", ".dependency.java", ".dependency1.kt", ".dependency2.kt")
if (File(dependencyPath).exists()) { for (suffix in dependencySuffixes) {
fixture.configureByFile(dependencyPath) val dependencyPath = testPath.replace(".kt", suffix)
} if (File(dependencyPath).exists()) {
val javaDependencyPath = testPath.replace(".kt", ".dependency.java") fixture.configureByFile(dependencyPath)
if (File(javaDependencyPath).exists()) { }
fixture.configureByFile(javaDependencyPath)
} }
fixture.configureByFile(testPath) fixture.configureByFile(testPath)
@@ -66,18 +66,26 @@ abstract class AbstractImportsTest : KotlinLightCodeInsightFixtureTestCase() {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), true)) codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), true))
} }
project.executeWriteCommand("") { val log = project.executeWriteCommand<String?>("") { doTest(file) }
doTest(file)
}
KotlinTestUtils.assertEqualsToFile(File(testPath + ".after"), myFixture.file.text) 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 { finally {
settingManager.dropTemporarySettings() settingManager.dropTemporarySettings()
} }
} }
protected abstract fun doTest(file: KtFile) // returns test log
protected abstract fun doTest(file: KtFile): String?
protected open val nameCountToUseStarImportDefault: Int protected open val nameCountToUseStarImportDefault: Int
get() = 1 get() = 1
@@ -29,7 +29,7 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils
abstract class AbstractAddImportTest : AbstractImportsTest() { abstract class AbstractAddImportTest : AbstractImportsTest() {
override fun doTest(file: KtFile) { override fun doTest(file: KtFile): String? {
var descriptorName = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// IMPORT:") var descriptorName = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// IMPORT:")
?: error("No IMPORT directive defined") ?: error("No IMPORT directive defined")
@@ -57,5 +57,7 @@ abstract class AbstractAddImportTest : AbstractImportsTest() {
} }
} }
} }
return null
} }
} }
@@ -16,17 +16,19 @@
package org.jetbrains.kotlin.idea.imports package org.jetbrains.kotlin.idea.imports
import com.intellij.openapi.command.CommandProcessor import org.jetbrains.kotlin.AbstractImportsTest
import com.intellij.openapi.command.UndoConfirmationPolicy import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import org.junit.Assert
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.psi.*
abstract class AbstractOptimizeImportsTest() : AbstractImportsTest() { abstract class AbstractOptimizeImportsTest() : AbstractImportsTest() {
override fun doTest(file: KtFile) { override fun doTest(file: KtFile): String {
KotlinImportOptimizer().processFile(file).run() OptimizedImportsBuilder.testLog = StringBuilder()
try {
KotlinImportOptimizer().processFile(file).run()
return OptimizedImportsBuilder.testLog.toString()
}
finally {
OptimizedImportsBuilder.testLog = null
}
} }
override val nameCountToUseStarImportDefault: Int override val nameCountToUseStarImportDefault: Int
@@ -155,6 +155,18 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest {
doTest(fileName); 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") @TestMetadata("KT13766.kt")
public void testKT13766() throws Exception { public void testKT13766() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/KT13766.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/KT13766.kt");
@@ -215,6 +227,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest {
doTest(fileName); 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") @TestMetadata("RemoveImportsIfGeneral.kt")
public void testRemoveImportsIfGeneral() throws Exception { public void testRemoveImportsIfGeneral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/RemoveImportsIfGeneral.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/RemoveImportsIfGeneral.kt");
@@ -251,6 +269,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest {
doTest(fileName); 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") @TestMetadata("TypeAliasUsage.kt")
public void testTypeAliasUsage() throws Exception { public void testTypeAliasUsage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/TypeAliasUsage.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/TypeAliasUsage.kt");
@@ -16,17 +16,18 @@
package org.jetbrains.kotlin.shortenRefs 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.AbstractImportsTest
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.psi.KtFile
abstract class AbstractShortenRefsTest : AbstractImportsTest() { abstract class AbstractShortenRefsTest : AbstractImportsTest() {
override fun doTest(file: KtFile) { override fun doTest(file: KtFile): String? {
val selectionModel = myFixture.editor.selectionModel val selectionModel = myFixture.editor.selectionModel
if (!selectionModel.hasSelection()) error("No selection in input file") if (!selectionModel.hasSelection()) error("No selection in input file")
ShortenReferences { ShortenReferences.Options(removeThis = true, removeThisLabels = true) } ShortenReferences { ShortenReferences.Options(removeThis = true, removeThisLabels = true) }
.process(file, selectionModel.selectionStart, selectionModel.selectionEnd) .process(file, selectionModel.selectionStart, selectionModel.selectionEnd)
selectionModel.removeSelection() selectionModel.removeSelection()
return null
} }
override val nameCountToUseStarImportDefault: Int override val nameCountToUseStarImportDefault: Int