Unused import highlighting

#KT-5236 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-07-13 20:01:54 +03:00
parent 8c02ce73a4
commit e5d8eecc9d
7 changed files with 182 additions and 36 deletions
+8 -1
View File
@@ -1046,7 +1046,7 @@
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection"
displayName="Unused Symbol"
displayName="Unused symbol"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
@@ -1059,6 +1059,13 @@
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedImportInspection"
displayName="Unused import directive"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.kdoc.KDocUnresolvedReferenceInspection"
displayName="Unresolved reference in KDoc"
groupName="Kotlin"
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -173,52 +174,63 @@ public class KotlinImportOptimizer() : ImportOptimizer {
}
private fun detectDescriptorsToImport(): Set<DeclarationDescriptor> {
val usedDescriptors = HashSet<DeclarationDescriptor>()
file.accept(object : JetVisitorVoid() {
override fun visitElement(element: PsiElement) {
ProgressIndicatorProvider.checkCanceled()
element.acceptChildren(this)
}
val visitor = CollectUsedDescriptorsVisitor(file, recursive = true)
file.accept(visitor)
return visitor.descriptors
}
}
override fun visitImportList(importList: JetImportList) {
}
public class CollectUsedDescriptorsVisitor(val file: JetFile, val recursive: Boolean) : JetVisitorVoid() {
private val _descriptors = HashSet<DeclarationDescriptor>()
override fun visitPackageDirective(directive: JetPackageDirective) {
}
public val descriptors: Set<DeclarationDescriptor>
get() = _descriptors
override fun visitJetElement(element: JetElement) {
for (reference in element.getReferences()) {
if (reference is JetReference) {
val referencedName = (element as? JetNameReferenceExpression)?.getReferencedNameAsName() //TODO: other types of references
override fun visitElement(element: PsiElement) {
if (recursive) {
ProgressIndicatorProvider.checkCanceled()
element.acceptChildren(this)
}
}
val bindingContext = element.analyze()
//class qualifiers that refer to companion objects should be considered (containing) class references
val targets = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? JetReferenceExpression]?.let { listOf(it) }
?: reference.resolveToDescriptors(bindingContext)
for (target in targets) {
if (!target.canBeReferencedViaImport()) continue
if (target is PackageViewDescriptor && target.fqName.parent() == FqName.ROOT) continue // no need to import top-level packages
override fun visitImportList(importList: JetImportList) {
}
if (!target.isExtension) { // for non-extension targets, count only non-qualified simple name usages
if (element !is JetNameReferenceExpression) continue
if (element.getIdentifier() == null) continue // skip 'this' etc
if (element.getReceiverExpression() != null) continue
}
override fun visitPackageDirective(directive: JetPackageDirective) {
}
val importableDescriptor = target.getImportableDescriptor()
if (referencedName != null && importableDescriptor.getName() != referencedName) continue // resolved via alias
override fun visitJetElement(element: JetElement) {
if (!recursive && element.parents.any { it is JetImportDirective || it is JetPackageDirective }) return
if (isAccessibleAsMember(importableDescriptor, element)) continue
for (reference in element.getReferences()) {
if (reference !is JetReference) continue
usedDescriptors.add(importableDescriptor)
}
}
val referencedName = (element as? JetNameReferenceExpression)?.getReferencedNameAsName() //TODO: other types of references
val bindingContext = element.analyze()
//class qualifiers that refer to companion objects should be considered (containing) class references
val targets = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? JetReferenceExpression]?.let { listOf(it) }
?: reference.resolveToDescriptors(bindingContext)
for (target in targets) {
if (!target.canBeReferencedViaImport()) continue
if (target is PackageViewDescriptor && target.fqName.parent() == FqName.ROOT) continue // no need to import top-level packages
if (!target.isExtension) { // for non-extension targets, count only non-qualified simple name usages
if (element !is JetNameReferenceExpression) continue
if (element.getIdentifier() == null) continue // skip 'this' etc
if (element.getReceiverExpression() != null) continue
}
super.visitJetElement(element)
val importableDescriptor = target.getImportableDescriptor()
if (referencedName != null && importableDescriptor.getName() != referencedName) continue // resolved via alias
if (isAccessibleAsMember(importableDescriptor, element)) continue
_descriptors.add(importableDescriptor)
}
})
return usedDescriptors
}
super.visitJetElement(element)
}
private fun isAccessibleAsMember(target: DeclarationDescriptor, place: JetElement): Boolean {
@@ -0,0 +1,85 @@
/*
* Copyright 2010-2015 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.inspections
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import java.util.HashSet
class UnusedImportInspection : AbstractKotlinInspection() {
private val visitorKey = Key<KotlinImportOptimizer.CollectUsedDescriptorsVisitor>("UnusedImportInspection.visitorKey")
override fun runForWholeFile() = true
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
val visitor = KotlinImportOptimizer.CollectUsedDescriptorsVisitor(session.getFile() as JetFile, recursive = false)
session.putUserData(visitorKey, visitor)
return visitor
}
override fun inspectionFinished(session: LocalInspectionToolSession, problemsHolder: ProblemsHolder) {
val usedDescriptors = session.getUserData(visitorKey)!!.descriptors
val fqNames = HashSet<FqName>()
val parentFqNames = HashSet<FqName>()
for (descriptor in usedDescriptors) {
val fqName = descriptor.importableFqNameSafe
fqNames.add(fqName)
val parentFqName = fqName.parent()
if (!parentFqName.isRoot()) {
parentFqNames.add(parentFqName)
}
}
val file = session.getFile() as JetFile
val directives = file.getImportDirectives()
for (directive in directives) {
val importPath = directive.getImportPath() ?: continue
if (importPath.getAlias() != null) continue // highlighting of unused alias imports not supported yet
val isUsed = if (importPath.isAllUnder()) {
importPath.fqnPart() in parentFqNames
}
else {
importPath.fqnPart() in fqNames
}
if (!isUsed) {
val nameExpression = directive.getImportedReference()?.getQualifiedElementSelector() as? JetSimpleNameExpression
if (nameExpression == null || nameExpression.getReferenceTargets(nameExpression.analyze()).isEmpty()) continue // do not highlight unresolved imports as unused
problemsHolder.registerProblem(
directive,
"Unused import directive",
ProblemHighlightType.LIKE_UNUSED_SYMBOL)
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
import java.io.* // unused
import java.sql.* // used
import java.util.HashMap // unused
import java.util.ArrayList // used
import java.unresolved.* // unused but unresolved
import java.net.Unresolved // unused but unresolved
import java.net.ConnectException as CE // highlighting of unused aliases not implemented yet
fun foo(list: ArrayList<String>) {
list.add("")
Date()
}
@@ -0,0 +1,20 @@
<problems>
<problem>
<file>file.kt</file>
<line>1</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="simple.kt" />
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unused import directive</problem_class>
<description>Unused import directive</description>
</problem>
<problem>
<file>file.kt</file>
<line>4</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="simple.kt" />
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unused import directive</problem_class>
<description>Unused import directive</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.UnusedImportInspection
@@ -94,6 +94,12 @@ public class JetInspectionTestGenerated extends AbstractJetInspectionTest {
doTest(fileName);
}
@TestMetadata("unusedImport/inspectionData/inspections.test")
public void testUnusedImport_inspectionData_Inspections_test() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/unusedImport/inspectionData/inspections.test");
doTest(fileName);
}
@TestMetadata("unusedReceiverParameter/inspectionData/inspections.test")
public void testUnusedReceiverParameter_inspectionData_Inspections_test() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/unusedReceiverParameter/inspectionData/inspections.test");