Unused import highlighting
#KT-5236 Fixed
This commit is contained in:
@@ -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
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user