![JAR search and dependency download from the Maven repository](/logo.png)
io.gitlab.arturbosch.detekt.rules.style.UnusedImports.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of detekt-rules-style Show documentation
Show all versions of detekt-rules-style Show documentation
Static code analysis for Kotlin
The newest version!
package io.gitlab.arturbosch.detekt.rules.style
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isPartOf
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.isDotReceiver
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
/**
* This rule reports unused imports. Unused imports are dead code and should be removed.
* Exempt from this rule are imports resulting from references to elements within KDoc and
* from destructuring declarations (componentN imports).
*/
@Suppress("ViolatesTypeResolutionRequirements")
class UnusedImports(config: Config) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Unused Imports are dead code and should be removed.",
Debt.FIVE_MINS
)
override fun visit(root: KtFile) {
with(UnusedImportsVisitor(bindingContext)) {
root.accept(this)
unusedImports().forEach {
report(CodeSmell(issue, Entity.from(it), "The import '${it.importedFqName}' is unused."))
}
}
super.visit(root)
}
private class UnusedImportsVisitor(private val bindingContext: BindingContext) : DetektVisitor() {
private var currentPackage: FqName? = null
private var imports: List? = null
private val namedReferences = mutableSetOf()
private val staticReferences = mutableSetOf()
private val namedReferencesInKDoc = mutableSetOf()
private val namedReferencesAsString: Set by lazy {
namedReferences.mapTo(mutableSetOf()) { it.text.trim('`') }
}
private val staticReferencesAsString: Set by lazy {
staticReferences.mapTo(mutableSetOf()) { it.text.trim('`') }
}
private val fqNames: Set by lazy {
namedReferences.mapNotNullTo(mutableSetOf()) { it.fqNameOrNull() }
}
/**
* All [namedReferences] whose [KtReferenceExpression.fqNameOrNull] cannot be resolved
* mapped to their text. String matches to such references shouldn't be marked as unused
* imports since they could match the unknown value being imported.
*/
@Suppress("CommentOverPrivateProperty")
private val unresolvedNamedReferencesAsString: Set by lazy {
namedReferences.mapNotNullTo(mutableSetOf()) {
if (it.fqNameOrNull() == null) it.text.trim('`') else null
}
}
fun unusedImports(): List {
fun KtImportDirective.isFromSamePackage() =
importedFqName?.parent() == currentPackage && alias == null
fun KtImportDirective.isNotUsed(): Boolean {
if (aliasName in (namedReferencesInKDoc + namedReferencesAsString)) return false
val identifier = identifier()
if (identifier in namedReferencesInKDoc || identifier in staticReferencesAsString) return false
return if (bindingContext == BindingContext.EMPTY) {
identifier !in namedReferencesAsString
} else {
val fqNameUsed = importPath?.fqName?.let { it in fqNames } == true
val unresolvedNameUsed = identifier in unresolvedNamedReferencesAsString
!fqNameUsed && !unresolvedNameUsed
}
}
return imports?.filter { it.isFromSamePackage() || it.isNotUsed() }.orEmpty()
}
override fun visitPackageDirective(directive: KtPackageDirective) {
currentPackage = directive.fqName
super.visitPackageDirective(directive)
}
override fun visitImportList(importList: KtImportList) {
imports = importList.imports.asSequence()
.filter { it.isValidImport }
.filter {
val identifier = it.identifier()
identifier?.contains("*")?.not() == true &&
!operatorSet.contains(identifier) &&
!componentNRegex.matches(identifier)
}
.toList()
super.visitImportList(importList)
}
override fun visitReferenceExpression(expression: KtReferenceExpression) {
expression
.takeIf { !it.isPartOf() && !it.isPartOf() }
?.takeIf { it.children.isEmpty() }
?.run {
if (this.isDotReceiver()) {
staticReferences.add(this)
} else {
namedReferences.add(this)
}
}
super.visitReferenceExpression(expression)
}
override fun visitDeclaration(dcl: KtDeclaration) {
val kdoc = dcl.docComment?.getAllSections()
kdoc?.forEach { kdocSection ->
kdocSection.getChildrenOfType()
.map { it.text }
.forEach { handleKDoc(it) }
handleKDoc(kdocSection.getContent())
}
super.visitDeclaration(dcl)
}
private fun handleKDoc(content: String) {
kotlinDocReferencesRegExp.findAll(content, 0)
.map { it.groupValues[1] }
.forEach {
val referenceNames = it.split(".")
namedReferencesInKDoc.add(referenceNames[0])
namedReferencesInKDoc.add(referenceNames.last())
}
kotlinDocBlockTagReferenceRegExp.find(content)?.let {
val str = it.groupValues[2].split(whiteSpaceRegex)[0]
namedReferencesInKDoc.add(str.split(".")[0])
}
}
private fun KtReferenceExpression.fqNameOrNull(): FqName? {
val descriptor = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, this]
?: bindingContext[BindingContext.REFERENCE_TARGET, this]
return descriptor?.getImportableDescriptor()?.fqNameOrNull()
}
}
companion object {
private val operatorSet = setOf(
"unaryPlus", "unaryMinus", "not", "inc", "dec", "plus", "minus", "times", "div",
"mod", "rangeTo", "rangeUntil", "contains", "get", "set", "invoke",
"plusAssign", "minusAssign", "timesAssign", "divAssign", "modAssign",
"equals", "compareTo", "iterator", "getValue", "setValue", "provideDelegate"
)
private val kotlinDocReferencesRegExp = Regex("\\[([^]]+)](?!\\[)")
private val kotlinDocBlockTagReferenceRegExp = Regex("^@(see|throws|exception) (.+)")
private val whiteSpaceRegex = Regex("\\s+")
private val componentNRegex = Regex("component\\d+")
}
}
private fun KtImportDirective.identifier() = this.importPath?.importedName?.identifier
© 2015 - 2025 Weber Informatics LLC | Privacy Policy