From 69b483783005de910f1aa7acc3b8149a7a45fb76 Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Tue, 26 Apr 2016 20:34:44 +0300 Subject: [PATCH] Move part of code that related to 'Optimize imports' action to ide-common module --- .../kotlin/idea/util/ImportInsertHelper.kt | 2 +- .../kotlin/idea/util/ImportsUtils.kt | 151 +++++++++++++++++- .../idea/imports/KotlinImportOptimizer.kt | 115 ------------- .../idea/util/ImportInsertHelperImpl.kt | 26 +-- 4 files changed, 153 insertions(+), 141 deletions(-) rename idea/{idea-analysis => ide-common}/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt (97%) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt similarity index 97% rename from idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt rename to idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt index 88e8f222097..d69f2c2fff2 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. 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 ffe1435cd20..d817de5dae5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -19,14 +19,48 @@ 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.* + +object ImportPathComparator : Comparator { + override fun compare(import1: ImportPath, import2: ImportPath): Int { + // alias imports placed last + if (import1.hasAlias() != import2.hasAlias()) { + return if (import1.hasAlias()) +1 else -1 + } + + // standard library imports last + val stdlib1 = isJavaOrKotlinStdlibImport(import1) + val stdlib2 = isJavaOrKotlinStdlibImport(import2) + if (stdlib1 != stdlib2) { + return if (stdlib1) +1 else -1 + } + + return import1.toString().compareTo(import2.toString()) + } + + private fun isJavaOrKotlinStdlibImport(path: ImportPath): Boolean { + val s = path.pathStr + return s.startsWith("java.") || s.startsWith("javax.")|| s.startsWith("kotlin.") + } +} val DeclarationDescriptor.importableFqName: FqName? get() { @@ -62,3 +96,118 @@ fun KtReferenceExpression.getImportableTargets(bindingContext: BindingContext): ?: getReferenceTargets(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/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt index 5b2064a9163..aa0539912b9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt @@ -166,121 +166,6 @@ class KotlinImportOptimizer() : ImportOptimizer { } } - 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 - } - fun replaceImports(file: KtFile, imports: List) { val importList = file.importList!! val oldImports = importList.imports diff --git a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt index 8e0aaf9253a..f21776dc486 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings import org.jetbrains.kotlin.idea.core.targetDescriptors +import org.jetbrains.kotlin.idea.imports.ImportPathComparator import org.jetbrains.kotlin.idea.imports.getImportableTargets import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.project.platform @@ -57,29 +58,6 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper( override val importSortComparator: Comparator get() = ImportPathComparator - private object ImportPathComparator : Comparator { - override fun compare(import1: ImportPath, import2: ImportPath): Int { - // alias imports placed last - if (import1.hasAlias() != import2.hasAlias()) { - return if (import1.hasAlias()) +1 else -1 - } - - // standard library imports last - val stdlib1 = isJavaOrKotlinStdlibImport(import1) - val stdlib2 = isJavaOrKotlinStdlibImport(import2) - if (stdlib1 != stdlib2) { - return if (stdlib1) +1 else -1 - } - - return import1.toString().compareTo(import2.toString()) - } - - private fun isJavaOrKotlinStdlibImport(path: ImportPath): Boolean { - val s = path.pathStr - return s.startsWith("java.") || s.startsWith("javax.")|| s.startsWith("kotlin.") - } - } - override fun isImportedWithDefault(importPath: ImportPath, contextFile: KtFile): Boolean { val defaultImports = contextFile.platform.defaultModuleParameters.defaultImports return importPath.isImported(defaultImports)