blob: 3b0e1b8f4cad4c8a24f73539009f23f166d7d340 [file] [log] [blame]
package com.github.shyiko.ktlint.ruleset.standard
import com.github.shyiko.ktlint.core.Rule
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class NoUnusedImportsRule : Rule("no-unused-imports") {
private val componentNRegex = Regex("^component\\d+$")
private val operatorSet = setOf(
// unary
"unaryPlus", "unaryMinus", "not",
// inc/dec
"inc", "dec",
// arithmetic
"plus", "minus", "times", "div", "rem", "mod", "rangeTo",
// in
"contains",
// indexed access
"get", "set",
// invoke
"invoke",
// augmented assignments
"plusAssign", "minusAssign", "timesAssign", "divAssign", "modAssign",
// (in)equality
"equals",
// comparison
"compareTo",
// iteration (https://github.com/shyiko/ktlint/issues/40)
"iterator",
// by (https://github.com/shyiko/ktlint/issues/54)
"getValue", "setValue"
)
private val ref = mutableSetOf<String>()
private var packageName = ""
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == KtStubElementTypes.FILE) {
ref.clear() // rule can potentially be executed more than once (when formatting)
ref.add("*")
node.visit { vnode ->
val psi = vnode.psi
val type = vnode.elementType
if (type == KDocTokens.MARKDOWN_LINK && psi is KDocLink) {
val linkText = psi.getLinkText().replace("`", "")
ref.add(linkText.split('.').first())
} else if ((type == KtNodeTypes.REFERENCE_EXPRESSION || type == KtNodeTypes.OPERATION_REFERENCE) &&
!psi.isPartOf(KtImportDirective::class)) {
ref.add(vnode.text.trim('`'))
}
}
} else if (node.elementType == KtStubElementTypes.PACKAGE_DIRECTIVE) {
val packageDirective = node.psi as KtPackageDirective
packageName = packageDirective.qualifiedName
} else if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
val importDirective = node.psi as KtImportDirective
val name = importDirective.importPath?.importedName?.asString()
val importPath = importDirective.importPath?.pathStr!!
if (importDirective.aliasName == null &&
importPath.startsWith(packageName) &&
importPath.substring(packageName.length + 1).indexOf('.') == -1) {
emit(importDirective.startOffset, "Unnecessary import", true)
if (autoCorrect) {
importDirective.delete()
}
} else if (name != null && !ref.contains(name) && !operatorSet.contains(name) && !name.isComponentN()) {
emit(importDirective.startOffset, "Unused import", true)
if (autoCorrect) {
importDirective.delete()
}
}
}
}
private fun String.isComponentN() = componentNRegex.matches(this)
}