Minor: refactoring KotlinImportOptimizer, KotlinUnusedImportInspection, OptimizedmportsBuilder and AbstractOptimizeImportsTest
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user