Minor: refactoring KotlinImportOptimizer, KotlinUnusedImportInspection, OptimizedmportsBuilder and AbstractOptimizeImportsTest

This commit is contained in:
Dmitry Gridin
2019-04-25 16:28:59 +07:00
parent 2f835ed66f
commit c52abfda16
4 changed files with 70 additions and 57 deletions
@@ -44,9 +44,9 @@ import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import java.util.*
class OptimizedImportsBuilder(
private val file: KtFile,
private val data: InputData,
private val options: Options
private val file: KtFile,
private val data: InputData,
private val options: Options
) {
companion object {
@get:TestOnly
@@ -61,14 +61,14 @@ class OptimizedImportsBuilder(
}
data class InputData(
val descriptorsToImport: Set<DeclarationDescriptor>,
val references: Collection<AbstractReference>
val descriptorsToImport: Set<DeclarationDescriptor>,
val references: Collection<AbstractReference>
)
data class Options(
val nameCountToUseStarImport: Int,
val nameCountToUseStarImportForMembers: Int,
val isInPackagesToUseStarImport: (FqName) -> Boolean
val nameCountToUseStarImport: Int,
val nameCountToUseStarImportForMembers: Int,
val isInPackagesToUseStarImport: (FqName) -> Boolean
)
private val importInsertHelper = ImportInsertHelper.getInstance(file.project)
@@ -76,12 +76,12 @@ class OptimizedImportsBuilder(
private sealed class ImportRule {
// force presence of this import
data class Add(val importPath: ImportPath) : ImportRule() {
override fun toString() = "+" + importPath.toString()
override fun toString() = "+$importPath"
}
// force absence of this import
data class DoNotAdd(val importPath: ImportPath) : ImportRule() {
override fun toString() = "-" + importPath.toString()
override fun toString() = "-$importPath"
}
}
@@ -91,12 +91,12 @@ class OptimizedImportsBuilder(
// TODO: should we drop unused aliases?
// keep all non-trivial aliases
file.importDirectives
.mapNotNull { it.importPath }
.filter {
val aliasName = it.alias
aliasName != null && aliasName != it.fqName.shortName()
}
.mapTo(importRules) { ImportRule.Add(it) }
.mapNotNull { it.importPath }
.filter {
val aliasName = it.alias
aliasName != null && aliasName != it.fqName.shortName()
}
.mapTo(importRules) { ImportRule.Add(it) }
while (true) {
val importRulesBefore = importRules.size
@@ -120,8 +120,8 @@ class OptimizedImportsBuilder(
private fun tryBuildOptimizedImports(): List<ImportPath>? {
val importsToGenerate = HashSet<ImportPath>()
importRules
.filterIsInstance<ImportRule.Add>()
.mapTo(importsToGenerate) { it.importPath }
.filterIsInstance<ImportRule.Add>()
.mapTo(importsToGenerate) { it.importPath }
val descriptorsByParentFqName = HashMap<FqName, MutableSet<DeclarationDescriptor>>()
for (descriptor in data.descriptorsToImport) {
@@ -134,8 +134,7 @@ class OptimizedImportsBuilder(
val starImportPath = ImportPath(parentFqName, true)
if (canUseStarImport(descriptor, fqName) && starImportPath.isAllowedByRules()) {
descriptorsByParentFqName.getOrPut(parentFqName) { HashSet() }.add(descriptor)
}
else {
} else {
importsToGenerate.add(explicitImportPath)
}
}
@@ -150,18 +149,17 @@ class OptimizedImportsBuilder(
val fqNames = descriptors.map { it.importableFqName!! }.toSet()
val nameCountToUseStar = descriptors.first().nameCountToUseStar()
val useExplicitImports = fqNames.size < nameCountToUseStar && !options.isInPackagesToUseStarImport(parentFqName)
|| !starImportPath.isAllowedByRules()
|| !starImportPath.isAllowedByRules()
if (useExplicitImports) {
fqNames
.filter { !isImportedByDefault(it) }
.mapTo(importsToGenerate) { ImportPath(it, false) }
}
else {
.filter { !isImportedByDefault(it) }
.mapTo(importsToGenerate) { ImportPath(it, false) }
} else {
descriptors
.asSequence()
.filterIsInstance<ClassDescriptor>()
.map { it.importableFqName!! }
.filterTo(classNamesToCheck) { !isImportedByDefault(it) }
.asSequence()
.filterIsInstance<ClassDescriptor>()
.map { it.importableFqName!! }
.filterTo(classNamesToCheck) { !isImportedByDefault(it) }
if (!fqNames.all(this::isImportedByDefault)) {
importsToGenerate.add(starImportPath)
@@ -192,8 +190,14 @@ class OptimizedImportsBuilder(
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.analyzeAsReplacement(expressionToAnalyze, bindingContext, newScope, trace = BindingTraceContext())
val newScope =
element.getResolutionScope(bindingContext, file.getResolutionFacade()).replaceImportingScopes(newFileScope)
val newBindingContext = expressionToAnalyze.analyzeAsReplacement(
expressionToAnalyze,
bindingContext,
newScope,
trace = BindingTraceContext()
)
testLog?.append("Additional checking of reference $ref\n")
@@ -226,10 +230,10 @@ class OptimizedImportsBuilder(
}
private fun addExplicitImportsForClassesWhenRequired(
classNamesToCheck: Collection<FqName>,
descriptorsByParentFqName: Map<FqName, MutableSet<DeclarationDescriptor>>,
importsToGenerate: MutableSet<ImportPath>,
originalFile: KtFile
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) {
@@ -239,7 +243,7 @@ class OptimizedImportsBuilder(
val parentFqName = fqName.parent()
val siblingsToImport = descriptorsByParentFqName[parentFqName]!!
val siblingsToImport = descriptorsByParentFqName.getValue(parentFqName)
for (descriptor in siblingsToImport.filter { it.importableFqName == fqName }) {
siblingsToImport.remove(descriptor)
}
@@ -274,7 +278,8 @@ class OptimizedImportsBuilder(
return fileWithImports.getFileResolutionScope()
}
private fun KtFile.getFileResolutionScope() = getResolutionFacade().frontendService<FileScopeProvider>().getFileScopes(this).importingScope
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)
@@ -292,14 +297,14 @@ class OptimizedImportsBuilder(
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) +
listOfNotNull(scope.getContributedClassifier(name, NoLookupLocation.FROM_IDE))
}
.map { scope ->
names.flatMap { name ->
scope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) +
scope.getContributedVariables(name, NoLookupLocation.FROM_IDE) +
listOfNotNull(scope.getContributedClassifier(name, NoLookupLocation.FROM_IDE))
}
.filter { it.isNotEmpty() }
}
.filter { it.isNotEmpty() }
}
private fun canUseStarImport(descriptor: DeclarationDescriptor, fqName: FqName): Boolean {
@@ -127,15 +127,15 @@ class KotlinImportOptimizer : ImportOptimizer {
return when (target) {
is FunctionDescriptor ->
scope.findFunction(target.name, NoLookupLocation.FROM_IDE) { it == target } != null
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
is PropertyDescriptor ->
scope.findVariable(target.name, NoLookupLocation.FROM_IDE) { it == target } != null
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
is ClassDescriptor ->
scope.findClassifier(target.name, NoLookupLocation.FROM_IDE) == target
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
else -> false
}
@@ -159,7 +159,8 @@ class KotlinImportOptimizer : ImportOptimizer {
get() {
val resolvesByNames = reference.resolvesByNames
if (reference is KtInvokeFunctionReference) {
val additionalNames = (reference.element.calleeExpression as? KtNameReferenceExpression)?.mainReference?.resolvesByNames
val additionalNames = (reference.element.calleeExpression as? KtNameReferenceExpression)
?.mainReference?.resolvesByNames
if (additionalNames != null) {
return resolvesByNames + additionalNames
}
@@ -183,9 +184,9 @@ class KotlinImportOptimizer : ImportOptimizer {
fun prepareOptimizedImports(file: KtFile, data: OptimizedImportsBuilder.InputData): List<ImportPath>? {
val settings = KotlinCodeStyleSettings.getInstance(file.project)
val options = OptimizedImportsBuilder.Options(
settings.NAME_COUNT_TO_USE_STAR_IMPORT,
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS,
isInPackagesToUseStarImport = { fqName -> fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS })
settings.NAME_COUNT_TO_USE_STAR_IMPORT,
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()
}
@@ -194,7 +195,10 @@ class KotlinImportOptimizer : ImportOptimizer {
val oldImports = importList.imports
val psiFactory = KtPsiFactory(file.project)
for (importPath in imports) {
importList.addBefore(psiFactory.createImportDirective(importPath), oldImports.lastOrNull()) // insert into the middle to keep collapsed state
importList.addBefore(
psiFactory.createImportDirective(importPath),
oldImports.lastOrNull()
) // insert into the middle to keep collapsed state
}
// remove old imports after adding new ones to keep imports folding state
@@ -206,7 +210,7 @@ class KotlinImportOptimizer : ImportOptimizer {
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)
?: resolveToDescriptors(bindingContext)
}
}
}
@@ -161,7 +161,12 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
ApplicationManager.getApplication().invokeLater {
val editor = PsiUtilBase.findEditor(file)
val currentModificationCount = PsiModificationTracker.SERVICE.getInstance(project).modificationCount
if (editor != null && currentModificationCount == modificationCount && timeToOptimizeImportsOnTheFly(file, editor, project)) {
if (editor != null && currentModificationCount == modificationCount && timeToOptimizeImportsOnTheFly(
file,
editor,
project
)
) {
optimizeImportsOnTheFly(file, optimizedImports, editor, project)
}
}
@@ -9,14 +9,13 @@ import org.jetbrains.kotlin.AbstractImportsTest
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.idea.test.KotlinStdJSProjectDescriptor
abstract class AbstractOptimizeImportsTest() : AbstractImportsTest() {
abstract class AbstractOptimizeImportsTest : AbstractImportsTest() {
override fun doTest(file: KtFile): String {
OptimizedImportsBuilder.testLog = StringBuilder()
try {
KotlinImportOptimizer().processFile(file).run()
return OptimizedImportsBuilder.testLog.toString()
}
finally {
} finally {
OptimizedImportsBuilder.testLog = null
}
}