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.*
|
import java.util.*
|
||||||
|
|
||||||
class OptimizedImportsBuilder(
|
class OptimizedImportsBuilder(
|
||||||
private val file: KtFile,
|
private val file: KtFile,
|
||||||
private val data: InputData,
|
private val data: InputData,
|
||||||
private val options: Options
|
private val options: Options
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@get:TestOnly
|
@get:TestOnly
|
||||||
@@ -61,14 +61,14 @@ class OptimizedImportsBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class InputData(
|
data class InputData(
|
||||||
val descriptorsToImport: Set<DeclarationDescriptor>,
|
val descriptorsToImport: Set<DeclarationDescriptor>,
|
||||||
val references: Collection<AbstractReference>
|
val references: Collection<AbstractReference>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Options(
|
data class Options(
|
||||||
val nameCountToUseStarImport: Int,
|
val nameCountToUseStarImport: Int,
|
||||||
val nameCountToUseStarImportForMembers: Int,
|
val nameCountToUseStarImportForMembers: Int,
|
||||||
val isInPackagesToUseStarImport: (FqName) -> Boolean
|
val isInPackagesToUseStarImport: (FqName) -> Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
private val importInsertHelper = ImportInsertHelper.getInstance(file.project)
|
private val importInsertHelper = ImportInsertHelper.getInstance(file.project)
|
||||||
@@ -76,12 +76,12 @@ class OptimizedImportsBuilder(
|
|||||||
private sealed class ImportRule {
|
private sealed class ImportRule {
|
||||||
// force presence of this import
|
// force presence of this import
|
||||||
data class Add(val importPath: ImportPath) : ImportRule() {
|
data class Add(val importPath: ImportPath) : ImportRule() {
|
||||||
override fun toString() = "+" + importPath.toString()
|
override fun toString() = "+$importPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
// force absence of this import
|
// force absence of this import
|
||||||
data class DoNotAdd(val importPath: ImportPath) : ImportRule() {
|
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?
|
// TODO: should we drop unused aliases?
|
||||||
// keep all non-trivial aliases
|
// keep all non-trivial aliases
|
||||||
file.importDirectives
|
file.importDirectives
|
||||||
.mapNotNull { it.importPath }
|
.mapNotNull { it.importPath }
|
||||||
.filter {
|
.filter {
|
||||||
val aliasName = it.alias
|
val aliasName = it.alias
|
||||||
aliasName != null && aliasName != it.fqName.shortName()
|
aliasName != null && aliasName != it.fqName.shortName()
|
||||||
}
|
}
|
||||||
.mapTo(importRules) { ImportRule.Add(it) }
|
.mapTo(importRules) { ImportRule.Add(it) }
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val importRulesBefore = importRules.size
|
val importRulesBefore = importRules.size
|
||||||
@@ -120,8 +120,8 @@ class OptimizedImportsBuilder(
|
|||||||
private fun tryBuildOptimizedImports(): List<ImportPath>? {
|
private fun tryBuildOptimizedImports(): List<ImportPath>? {
|
||||||
val importsToGenerate = HashSet<ImportPath>()
|
val importsToGenerate = HashSet<ImportPath>()
|
||||||
importRules
|
importRules
|
||||||
.filterIsInstance<ImportRule.Add>()
|
.filterIsInstance<ImportRule.Add>()
|
||||||
.mapTo(importsToGenerate) { it.importPath }
|
.mapTo(importsToGenerate) { it.importPath }
|
||||||
|
|
||||||
val descriptorsByParentFqName = HashMap<FqName, MutableSet<DeclarationDescriptor>>()
|
val descriptorsByParentFqName = HashMap<FqName, MutableSet<DeclarationDescriptor>>()
|
||||||
for (descriptor in data.descriptorsToImport) {
|
for (descriptor in data.descriptorsToImport) {
|
||||||
@@ -134,8 +134,7 @@ class OptimizedImportsBuilder(
|
|||||||
val starImportPath = ImportPath(parentFqName, true)
|
val starImportPath = ImportPath(parentFqName, true)
|
||||||
if (canUseStarImport(descriptor, fqName) && starImportPath.isAllowedByRules()) {
|
if (canUseStarImport(descriptor, fqName) && starImportPath.isAllowedByRules()) {
|
||||||
descriptorsByParentFqName.getOrPut(parentFqName) { HashSet() }.add(descriptor)
|
descriptorsByParentFqName.getOrPut(parentFqName) { HashSet() }.add(descriptor)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
importsToGenerate.add(explicitImportPath)
|
importsToGenerate.add(explicitImportPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,18 +149,17 @@ class OptimizedImportsBuilder(
|
|||||||
val fqNames = descriptors.map { it.importableFqName!! }.toSet()
|
val fqNames = descriptors.map { it.importableFqName!! }.toSet()
|
||||||
val nameCountToUseStar = descriptors.first().nameCountToUseStar()
|
val nameCountToUseStar = descriptors.first().nameCountToUseStar()
|
||||||
val useExplicitImports = fqNames.size < nameCountToUseStar && !options.isInPackagesToUseStarImport(parentFqName)
|
val useExplicitImports = fqNames.size < nameCountToUseStar && !options.isInPackagesToUseStarImport(parentFqName)
|
||||||
|| !starImportPath.isAllowedByRules()
|
|| !starImportPath.isAllowedByRules()
|
||||||
if (useExplicitImports) {
|
if (useExplicitImports) {
|
||||||
fqNames
|
fqNames
|
||||||
.filter { !isImportedByDefault(it) }
|
.filter { !isImportedByDefault(it) }
|
||||||
.mapTo(importsToGenerate) { ImportPath(it, false) }
|
.mapTo(importsToGenerate) { ImportPath(it, false) }
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
descriptors
|
descriptors
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filterIsInstance<ClassDescriptor>()
|
.filterIsInstance<ClassDescriptor>()
|
||||||
.map { it.importableFqName!! }
|
.map { it.importableFqName!! }
|
||||||
.filterTo(classNamesToCheck) { !isImportedByDefault(it) }
|
.filterTo(classNamesToCheck) { !isImportedByDefault(it) }
|
||||||
|
|
||||||
if (!fqNames.all(this::isImportedByDefault)) {
|
if (!fqNames.all(this::isImportedByDefault)) {
|
||||||
importsToGenerate.add(starImportPath)
|
importsToGenerate.add(starImportPath)
|
||||||
@@ -192,8 +190,14 @@ class OptimizedImportsBuilder(
|
|||||||
val element = ref.element
|
val element = ref.element
|
||||||
val bindingContext = element.analyze()
|
val bindingContext = element.analyze()
|
||||||
val expressionToAnalyze = getExpressionToAnalyze(element) ?: continue
|
val expressionToAnalyze = getExpressionToAnalyze(element) ?: continue
|
||||||
val newScope = element.getResolutionScope(bindingContext, file.getResolutionFacade()).replaceImportingScopes(newFileScope)
|
val newScope =
|
||||||
val newBindingContext = expressionToAnalyze.analyzeAsReplacement(expressionToAnalyze, bindingContext, newScope, trace = BindingTraceContext())
|
element.getResolutionScope(bindingContext, file.getResolutionFacade()).replaceImportingScopes(newFileScope)
|
||||||
|
val newBindingContext = expressionToAnalyze.analyzeAsReplacement(
|
||||||
|
expressionToAnalyze,
|
||||||
|
bindingContext,
|
||||||
|
newScope,
|
||||||
|
trace = BindingTraceContext()
|
||||||
|
)
|
||||||
|
|
||||||
testLog?.append("Additional checking of reference $ref\n")
|
testLog?.append("Additional checking of reference $ref\n")
|
||||||
|
|
||||||
@@ -226,10 +230,10 @@ class OptimizedImportsBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addExplicitImportsForClassesWhenRequired(
|
private fun addExplicitImportsForClassesWhenRequired(
|
||||||
classNamesToCheck: Collection<FqName>,
|
classNamesToCheck: Collection<FqName>,
|
||||||
descriptorsByParentFqName: Map<FqName, MutableSet<DeclarationDescriptor>>,
|
descriptorsByParentFqName: Map<FqName, MutableSet<DeclarationDescriptor>>,
|
||||||
importsToGenerate: MutableSet<ImportPath>,
|
importsToGenerate: MutableSet<ImportPath>,
|
||||||
originalFile: KtFile
|
originalFile: KtFile
|
||||||
) {
|
) {
|
||||||
val scope = buildScopeByImports(originalFile, importsToGenerate.filter { it.isAllUnder })
|
val scope = buildScopeByImports(originalFile, importsToGenerate.filter { it.isAllUnder })
|
||||||
for (fqName in classNamesToCheck) {
|
for (fqName in classNamesToCheck) {
|
||||||
@@ -239,7 +243,7 @@ class OptimizedImportsBuilder(
|
|||||||
|
|
||||||
val parentFqName = fqName.parent()
|
val parentFqName = fqName.parent()
|
||||||
|
|
||||||
val siblingsToImport = descriptorsByParentFqName[parentFqName]!!
|
val siblingsToImport = descriptorsByParentFqName.getValue(parentFqName)
|
||||||
for (descriptor in siblingsToImport.filter { it.importableFqName == fqName }) {
|
for (descriptor in siblingsToImport.filter { it.importableFqName == fqName }) {
|
||||||
siblingsToImport.remove(descriptor)
|
siblingsToImport.remove(descriptor)
|
||||||
}
|
}
|
||||||
@@ -274,7 +278,8 @@ class OptimizedImportsBuilder(
|
|||||||
return fileWithImports.getFileResolutionScope()
|
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 {
|
private fun areScopeSlicesEqual(scope1: ImportingScope, scope2: ImportingScope, names: Collection<Name>): Boolean {
|
||||||
val tower1 = scope1.extractSliceTower(names)
|
val tower1 = scope1.extractSliceTower(names)
|
||||||
@@ -292,14 +297,14 @@ class OptimizedImportsBuilder(
|
|||||||
|
|
||||||
private fun ImportingScope.extractSliceTower(names: Collection<Name>): Sequence<Collection<DeclarationDescriptor>> {
|
private fun ImportingScope.extractSliceTower(names: Collection<Name>): Sequence<Collection<DeclarationDescriptor>> {
|
||||||
return parentsWithSelf
|
return parentsWithSelf
|
||||||
.map { scope ->
|
.map { scope ->
|
||||||
names.flatMap { name ->
|
names.flatMap { name ->
|
||||||
scope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) +
|
scope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) +
|
||||||
scope.getContributedVariables(name, NoLookupLocation.FROM_IDE) +
|
scope.getContributedVariables(name, NoLookupLocation.FROM_IDE) +
|
||||||
listOfNotNull(scope.getContributedClassifier(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 {
|
private fun canUseStarImport(descriptor: DeclarationDescriptor, fqName: FqName): Boolean {
|
||||||
|
|||||||
@@ -127,15 +127,15 @@ class KotlinImportOptimizer : ImportOptimizer {
|
|||||||
return when (target) {
|
return when (target) {
|
||||||
is FunctionDescriptor ->
|
is FunctionDescriptor ->
|
||||||
scope.findFunction(target.name, NoLookupLocation.FROM_IDE) { it == target } != null
|
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 ->
|
is PropertyDescriptor ->
|
||||||
scope.findVariable(target.name, NoLookupLocation.FROM_IDE) { it == target } != null
|
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 ->
|
is ClassDescriptor ->
|
||||||
scope.findClassifier(target.name, NoLookupLocation.FROM_IDE) == target
|
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
|
else -> false
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,8 @@ class KotlinImportOptimizer : ImportOptimizer {
|
|||||||
get() {
|
get() {
|
||||||
val resolvesByNames = reference.resolvesByNames
|
val resolvesByNames = reference.resolvesByNames
|
||||||
if (reference is KtInvokeFunctionReference) {
|
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) {
|
if (additionalNames != null) {
|
||||||
return resolvesByNames + additionalNames
|
return resolvesByNames + additionalNames
|
||||||
}
|
}
|
||||||
@@ -183,9 +184,9 @@ class KotlinImportOptimizer : ImportOptimizer {
|
|||||||
fun prepareOptimizedImports(file: KtFile, data: OptimizedImportsBuilder.InputData): List<ImportPath>? {
|
fun prepareOptimizedImports(file: KtFile, data: OptimizedImportsBuilder.InputData): List<ImportPath>? {
|
||||||
val settings = KotlinCodeStyleSettings.getInstance(file.project)
|
val settings = KotlinCodeStyleSettings.getInstance(file.project)
|
||||||
val options = OptimizedImportsBuilder.Options(
|
val options = OptimizedImportsBuilder.Options(
|
||||||
settings.NAME_COUNT_TO_USE_STAR_IMPORT,
|
settings.NAME_COUNT_TO_USE_STAR_IMPORT,
|
||||||
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS,
|
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS,
|
||||||
isInPackagesToUseStarImport = { fqName -> 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()
|
return OptimizedImportsBuilder(file, data, options).buildOptimizedImports()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +195,10 @@ class KotlinImportOptimizer : ImportOptimizer {
|
|||||||
val oldImports = importList.imports
|
val oldImports = importList.imports
|
||||||
val psiFactory = KtPsiFactory(file.project)
|
val psiFactory = KtPsiFactory(file.project)
|
||||||
for (importPath in imports) {
|
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
|
// 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> {
|
private fun KtReference.targets(bindingContext: BindingContext): Collection<DeclarationDescriptor> {
|
||||||
//class qualifiers that refer to companion objects should be considered (containing) class references
|
//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) }
|
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 {
|
ApplicationManager.getApplication().invokeLater {
|
||||||
val editor = PsiUtilBase.findEditor(file)
|
val editor = PsiUtilBase.findEditor(file)
|
||||||
val currentModificationCount = PsiModificationTracker.SERVICE.getInstance(project).modificationCount
|
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)
|
optimizeImportsOnTheFly(file, optimizedImports, editor, project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ import org.jetbrains.kotlin.AbstractImportsTest
|
|||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinStdJSProjectDescriptor
|
import org.jetbrains.kotlin.idea.test.KotlinStdJSProjectDescriptor
|
||||||
|
|
||||||
abstract class AbstractOptimizeImportsTest() : AbstractImportsTest() {
|
abstract class AbstractOptimizeImportsTest : AbstractImportsTest() {
|
||||||
override fun doTest(file: KtFile): String {
|
override fun doTest(file: KtFile): String {
|
||||||
OptimizedImportsBuilder.testLog = StringBuilder()
|
OptimizedImportsBuilder.testLog = StringBuilder()
|
||||||
try {
|
try {
|
||||||
KotlinImportOptimizer().processFile(file).run()
|
KotlinImportOptimizer().processFile(file).run()
|
||||||
return OptimizedImportsBuilder.testLog.toString()
|
return OptimizedImportsBuilder.testLog.toString()
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
OptimizedImportsBuilder.testLog = null
|
OptimizedImportsBuilder.testLog = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user