Implement post-refactoring optimization of unused imports

#KT-15822 Fixed
 #KT-13755 Fixed
This commit is contained in:
Alexey Sedunov
2017-03-05 03:37:40 +03:00
parent 7d185349c8
commit bab762b761
82 changed files with 199 additions and 191 deletions
+1
View File
@@ -358,6 +358,7 @@
<lang.unwrapDescriptor language="kotlin" implementationClass="org.jetbrains.kotlin.idea.codeInsight.unwrap.KotlinUnwrapDescriptor"/>
<quoteHandler fileType="Kotlin" className="org.jetbrains.kotlin.idea.editor.KotlinQuoteHandler"/>
<refactoring.helper implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinShortenReferencesRefactoringHelper"/>
<refactoring.helper implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinOptimizeImportsRefactoringHelper"/>
<refactoring.moveHandler
id="kotlin.moveFilesOrDirectories"
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.KotlinMoveFilesOrDirectoriesHandler"
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2017 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.codeInsight
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.refactoring.RefactoringHelper
import com.intellij.usageView.UsageInfo
import com.intellij.util.IncorrectOperationException
import com.intellij.util.SequentialModalProgressTask
import com.intellij.util.SequentialTask
import org.jetbrains.kotlin.idea.inspections.KotlinUnusedImportInspection
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
// Based on com.intellij.refactoring.OptimizeImportsRefactoringHelper
class KotlinOptimizeImportsRefactoringHelper : RefactoringHelper<Set<KtFile>> {
internal class OptimizeImportsTask(
private val task: SequentialModalProgressTask,
pointers: Set<SmartPsiElementPointer<KtImportDirective>>
) : SequentialTask {
private val pointerIterator = pointers.iterator()
private val myTotal: Int = pointers.size
private var myCount: Int = 0
override fun prepare() {}
override fun isDone() = !pointerIterator.hasNext()
override fun iteration(): Boolean {
task.indicator?.fraction = myCount++.toDouble() / myTotal
val pointer = pointerIterator.next()
val directive = pointer.element
if (directive == null || !directive.isValid) return isDone
try {
directive.delete()
}
catch (e: IncorrectOperationException) {
LOG.error(e)
}
return isDone
}
override fun stop() {}
companion object {
private val LOG = Logger.getInstance("#" + OptimizeImportsTask::class.java.name)
}
}
companion object {
private val REMOVING_REDUNDANT_IMPORTS_TITLE = "Removing redundant imports"
}
override fun prepareOperation(usages: Array<UsageInfo>): Set<KtFile> {
return usages.mapNotNullTo(LinkedHashSet<KtFile>()) {
if (!it.isNonCodeUsage) it.file as? KtFile else null
}
}
override fun performOperation(project: Project, operationData: Set<KtFile>) {
CodeStyleManager.getInstance(project).performActionWithFormatterDisabled {
PsiDocumentManager.getInstance(project).commitAllDocuments()
}
if (operationData.isEmpty()) return
val unusedImports = LinkedHashSet<SmartPsiElementPointer<KtImportDirective>>()
val findRedundantImports = {
DumbService.getInstance(project).runReadActionInSmartMode {
val progressIndicator = ProgressManager.getInstance().progressIndicator
val fileCount = operationData.size
for ((i, file) in operationData.withIndex()) {
if (!file.isValid) continue
val virtualFile = file.virtualFile ?: continue
progressIndicator?.text2 = virtualFile.presentableUrl
progressIndicator?.fraction = i.toDouble() / fileCount
val fileImportData = KotlinUnusedImportInspection.analyzeImports(file) ?: continue
fileImportData.unusedImports.mapTo(unusedImports) { it.createSmartPointer() }
}
}
}
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
findRedundantImports, REMOVING_REDUNDANT_IMPORTS_TITLE, false, project
)) return
runWriteAction {
val progressTask = SequentialModalProgressTask(project, REMOVING_REDUNDANT_IMPORTS_TITLE, false).apply {
setMinIterationTime(200)
setTask(OptimizeImportsTask(this, unusedImports))
}
ProgressManager.getInstance().run(progressTask)
}
}
}
@@ -48,78 +48,94 @@ import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.*
class KotlinUnusedImportInspection : AbstractKotlinInspection() {
data class ImportData(
val unusedImports: List<KtImportDirective>,
val optimizerData: OptimizedImportsBuilder.InputData
)
companion object {
fun analyzeImports(file: KtFile): ImportData? {
if (file is KtCodeFragment) return null
if (!file.manager.isInProject(file)) return null
if (file.importDirectives.isEmpty()) return null
val optimizerData = KotlinImportOptimizer.collectDescriptorsToImport(file)
val directives = file.importDirectives
val explicitlyImportedFqNames = directives
.asSequence()
.mapNotNull { it.importPath }
.filter { !it.isAllUnder && !it.hasAlias() }
.map { it.fqName }
.toSet()
val fqNames = HashSet<FqName>()
val parentFqNames = HashSet<FqName>()
for (descriptor in optimizerData.descriptorsToImport) {
val fqName = descriptor.importableFqName!!
fqNames.add(fqName)
if (fqName !in explicitlyImportedFqNames) { // we don't add parents of explicitly imported fq-names because such imports are not needed
val parentFqName = fqName.parent()
if (!parentFqName.isRoot) {
parentFqNames.add(parentFqName)
}
}
}
val importPaths = HashSet<ImportPath>(directives.size)
val unusedImports = ArrayList<KtImportDirective>()
for (directive in directives) {
val importPath = directive.importPath ?: continue
if (importPath.alias != null) continue // highlighting of unused alias imports not supported yet
val isUsed = if (!importPaths.add(importPath)) {
false
}
else if (importPath.isAllUnder) {
importPath.fqName in parentFqNames
}
else {
importPath.fqName in fqNames
}
if (!isUsed) {
if (directive.targetDescriptors().isEmpty()) continue // do not highlight unresolved imports as unused
unusedImports += directive
}
}
return ImportData(unusedImports, optimizerData)
}
}
override fun runForWholeFile() = true
override fun checkFile(file: PsiFile, manager: InspectionManager, isOnTheFly: Boolean): Array<out ProblemDescriptor>? {
if (file !is KtFile || file is KtCodeFragment) return null
if (!file.manager.isInProject(file)) return null
if (file.importDirectives.isEmpty()) return null
if (file !is KtFile) return null
val data = analyzeImports(file) ?: return null
val data = KotlinImportOptimizer.collectDescriptorsToImport(file)
val directives = file.importDirectives
val explicitlyImportedFqNames = directives
.asSequence()
.mapNotNull { it.importPath }
.filter { !it.isAllUnder && !it.hasAlias() }
.map { it.fqName }
.toSet()
val fqNames = HashSet<FqName>()
val parentFqNames = HashSet<FqName>()
for (descriptor in data.descriptorsToImport) {
val fqName = descriptor.importableFqName!!
fqNames.add(fqName)
if (fqName !in explicitlyImportedFqNames) { // we don't add parents of explicitly imported fq-names because such imports are not needed
val parentFqName = fqName.parent()
if (!parentFqName.isRoot) {
parentFqNames.add(parentFqName)
}
}
}
val problems = ArrayList<ProblemDescriptor>()
val importPaths = HashSet<ImportPath>(directives.size)
for (directive in directives) {
val importPath = directive.importPath ?: continue
if (importPath.alias != null) continue // highlighting of unused alias imports not supported yet
val isUsed = if (!importPaths.add(importPath)) {
false
}
else if (importPath.isAllUnder) {
importPath.fqName in parentFqNames
}
else {
importPath.fqName in fqNames
}
if (!isUsed) {
if (directive.targetDescriptors().isEmpty()) continue // do not highlight unresolved imports as unused
val fixes = arrayListOf<LocalQuickFix>()
fixes.add(OptimizeImportsQuickFix(file))
if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) {
fixes.add(EnableOptimizeImportsOnTheFlyFix(file))
}
problems.add(manager.createProblemDescriptor(directive,
"Unused import directive",
isOnTheFly,
fixes.toTypedArray(),
ProblemHighlightType.LIKE_UNUSED_SYMBOL))
val problems = data.unusedImports.map {
val fixes = arrayListOf<LocalQuickFix>()
fixes.add(OptimizeImportsQuickFix(file))
if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) {
fixes.add(EnableOptimizeImportsOnTheFlyFix(file))
}
manager.createProblemDescriptor(it,
"Unused import directive",
isOnTheFly,
fixes.toTypedArray(),
ProblemHighlightType.LIKE_UNUSED_SYMBOL)
}
if (isOnTheFly) {
scheduleOptimizeImportsOnTheFly(file, data)
scheduleOptimizeImportsOnTheFly(file, data.optimizerData)
}
return problems.toTypedArray()
@@ -1,8 +1,5 @@
package `in`.foo.`fun`
import `in`.foo.`fun`.Foo
import `in`.foo.`fun`.foo
fun test() {
Foo()
foo()
@@ -1,6 +1,3 @@
import Foo
import foo
fun test() {
Foo()
foo()
@@ -1,8 +1,5 @@
package target
import target.Foo
import target.foo
fun test() {
Foo()
foo()
@@ -1,5 +1,3 @@
import J.bar
fun test() {
J.bar("1")
J.bar("3")
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
val t: B.C.X = B.C.X()
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
val t: B.X = B.X()
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
val t: B.X = B.X()
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
val t: B.X = B.X()
}
@@ -1,6 +1,5 @@
package k
import j.A.*
import j.B
fun bar(s: String) {
@@ -1,6 +1,5 @@
package k
import j.A
import j.B
fun bar(s: String) {
@@ -1,7 +1,5 @@
package b
import b.*
fun bar() {
val t: X = X()
}
@@ -1,7 +1,5 @@
package a
import a.A.*
fun bar(s: String) {
val t: X = X()
}
@@ -1,7 +1,5 @@
package a
import a.A.*
fun bar(s: String) {
val t: X = X(A(), "test")
}
@@ -1,7 +1,5 @@
package a
import a.X
fun bar(s: String) {
val t: X = X(A(), "test")
}
@@ -1,7 +1,5 @@
package a
import a.A.*
fun bar(s: String) {
val t: X = X(A()) { s }
}
@@ -1,7 +1,5 @@
package a
import a.X
fun bar(s: String) {
val t: X = X(A()) { s }
}
@@ -1,7 +1,5 @@
package a
import a.A.*
fun bar(s: String) {
val t: Y = Y()
}
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
val t: A = A()
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.A
fun bar() {
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.C.X = s
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.X = s
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.X = s
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.X = s
}
@@ -1,6 +1,5 @@
package k
import j.A.*
import j.B
fun bar(s: String) {
@@ -1,6 +1,5 @@
package k
import j.A
import j.B
fun bar(s: String) {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
val t: A = A()
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.C.foo(s)
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.foo(s)
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.foo(s)
}
@@ -1,5 +1,3 @@
import A.*
fun bar(s: String) {
B.foo(s)
}
@@ -1,6 +1,5 @@
package k
import j.A.*
import j.B
fun bar(s: String) {
@@ -1,6 +1,5 @@
package k
import j.A
import j.B
fun bar(s: String) {
@@ -1,6 +1,3 @@
import Foo
import foo
fun test() {
Foo()
foo()
@@ -1,8 +1,5 @@
package target
import target.Foo
import target.foo
fun test() {
Foo()
foo()
@@ -1,7 +1,6 @@
package b
import a.f2
import b.f3
import c.f4
fun test() {
@@ -1,6 +1,5 @@
package c
import a.*
import b.TEST
import b.Test
import b.test
@@ -1,7 +1,6 @@
package test2
import test.A
import test.A.B
import test.C
fun foo2(): C {
@@ -1,6 +1,5 @@
package test2
import test.A
import test.C
fun foo(): C {
@@ -1,6 +1,5 @@
package test2
import test.A.B
import test.C
fun foo(): C {
@@ -1,6 +1,5 @@
package test2
import test.A
import test.B
fun foo(): B {
@@ -1,6 +1,5 @@
package test2
import test.A
import test.B
fun foo2(): B {
@@ -1,7 +1,6 @@
package test2
import test.A
import test.A.B
fun foo(): A.C {
return A.C()
@@ -1,7 +1,6 @@
package test2
import test.A
import test.A.B
fun foo(): A.C {
return A.C()
@@ -1,6 +1,5 @@
package test2
import test.A
import test.B
fun foo(): B {
@@ -1,6 +1,5 @@
package test2
import test.A
import test3.D
fun foo(): D.C {
@@ -1,6 +1,5 @@
package test2
import test.A.B
import test3.D
fun foo(): D.C {
@@ -1,6 +1,5 @@
package test2
import test.A
import test.B
fun foo(): B {
@@ -1,5 +1,2 @@
package source
import library.B
import library.bar
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
val t: Test = Test()
}
@@ -1,7 +1,5 @@
package b
import b.Test
fun bar() {
val t: Test = Test()
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.Test
fun bar() {
@@ -1,5 +1,3 @@
package q.`in`.`fun`
import q.`in`.`fun`.To
class Usage : To() {}
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
val t: Test = Test()
}
@@ -1,7 +1,5 @@
package b
import b.Test
fun bar() {
val t: Test = Test()
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.Test
fun bar() {
@@ -1,6 +1,5 @@
package b
import b.test
import a.Test
fun bar() {
@@ -1,6 +1,5 @@
package b
import b.test
import a.Test
fun bar() {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
test()
}
@@ -1,7 +1,5 @@
package b
import b.test
fun bar() {
test()
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.test
fun bar() {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
test()
}
@@ -1,7 +1,5 @@
package b
import b.test
fun bar() {
test()
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.test
fun bar() {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
val t: Test = Test
}
@@ -1,7 +1,5 @@
package b
import b.Test
fun bar() {
val t: Test = Test
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.Test
fun bar() {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
val t: Test = Test
}
@@ -1,7 +1,5 @@
package b
import b.Test
fun bar() {
val t: Test = Test
}
@@ -1,6 +1,5 @@
package c
import a.*
import b.Test
fun bar() {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
test = ""
println(test)
@@ -1,7 +1,5 @@
package b
import b.test
fun bar() {
test = ""
println(test)
@@ -1,6 +1,5 @@
package c
import a.*
import b.test
fun bar() {
@@ -1,7 +1,5 @@
package b
import a.*
fun bar() {
test = ""
println(test)
@@ -1,7 +1,5 @@
package b
import b.test
fun bar() {
test = ""
println(test)
@@ -1,6 +1,5 @@
package c
import a.*
import b.test
fun bar() {
@@ -1,7 +1,5 @@
package some.bar.more
import some.bar.other.Test
fun test() {
some.bar.more.test()
some.bar.other.testFunction()
@@ -2,7 +2,6 @@ package com.myapp
import android.app.Activity
import android.os.Bundle
import java.io.File
import kotlinx.android.synthetic.main.layout.*
// KT-16132 Renaming property provided by kotlinx leads to renaming another members
@@ -2,7 +2,6 @@ package com.myapp
import android.app.Activity
import android.os.Bundle
import java.io.File
import kotlinx.android.synthetic.main.layout.*
public class MyActivity : Activity() {
@@ -1,7 +1,6 @@
package com.myapp
import android.app.Fragment
import java.io.File
import kotlinx.android.synthetic.main.layout.*
public class MyFragment : Fragment() {