Skip to content

Commit

Permalink
Fix for UNUSED_IMPORT in KDoc
Browse files Browse the repository at this point in the history
### What's done:
- supported KDOC_MARKDOWN_LINK as references
  • Loading branch information
nulls committed Jun 16, 2022
1 parent 12a39ab commit e1d8cd4
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder
import org.cqfn.diktat.ruleset.utils.ignoreImports
import org.cqfn.diktat.ruleset.utils.moveChildBefore
import org.cqfn.diktat.ruleset.utils.operatorMap
import org.cqfn.diktat.ruleset.utils.removePrefix

import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
Expand All @@ -27,6 +28,7 @@ import com.pinterest.ktlint.core.ast.ElementType.FILE_ANNOTATION_LIST
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
import com.pinterest.ktlint.core.ast.ElementType.KDOC
import com.pinterest.ktlint.core.ast.ElementType.KDOC_MARKDOWN_LINK
import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
Expand Down Expand Up @@ -73,9 +75,8 @@ class FileStructureRule(configRules: List<RulesConfig>) : DiktatRule(
.mapValues { (_, value) ->
value.map { it.split(PACKAGE_SEPARATOR).map(Name::identifier) }
}
private val refSet: MutableSet<String> = mutableSetOf()
private var packageName = ""

/**
* There are groups of methods, which should be excluded from usage check without type resolution.
* `componentN` is a method for N-th component in destructuring declarations.
Expand Down Expand Up @@ -226,7 +227,7 @@ class FileStructureRule(configRules: List<RulesConfig>) : DiktatRule(
private fun checkUnusedImport(
node: ASTNode
) {
findAllReferences(node)
val refSet = findAllReferences(node)
packageName = (node.findChildByType(PACKAGE_DIRECTIVE)?.psi as KtPackageDirective).qualifiedName
node.findChildByType(IMPORT_LIST)
?.getChildren(TokenSet.create(IMPORT_DIRECTIVE))
Expand Down Expand Up @@ -264,24 +265,36 @@ class FileStructureRule(configRules: List<RulesConfig>) : DiktatRule(
) { ktImportDirective.delete() }
}

private fun findAllReferences(node: ASTNode) {
node.findAllDescendantsWithSpecificType(OPERATION_REFERENCE).forEach { ref ->
if (!ref.isPartOf(IMPORT_DIRECTIVE)) {
private fun findAllReferences(node: ASTNode): Set<String> {
val referencesFromOperations = node.findAllDescendantsWithSpecificType(OPERATION_REFERENCE)
.filterNot { it.isPartOf(IMPORT_DIRECTIVE) }
.flatMap { ref ->
val references = operatorMap.filterValues { ref.text in it }
if (references.isNotEmpty()) {
references.keys.forEach { key -> refSet.add(key) }
references.keys
} else {
// this is needed to check infix functions that relate to operation reference
refSet.add(ref.text)
setOf(ref.text)
}
}
}
node.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION).forEach {
if (!it.isPartOf(IMPORT_DIRECTIVE)) {
val referencesFromExpressions = node.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION)
.filterNot { it.isPartOf(IMPORT_DIRECTIVE) }
.map {
// the importedName method removes the quotes, but the node.text method does not
refSet.add(it.text.replace("`", ""))
it.text.replace("`", "")
}
}
val referencesFromKDocs = node.findAllDescendantsWithSpecificType(KDOC)
.flatMap { it.findAllDescendantsWithSpecificType(KDOC_MARKDOWN_LINK) }
.map { it.text.removePrefix("[").removeSuffix("]") }
.flatMap {
if (it.contains(".")) {
// support cases with reference to method
listOf(it, it.substringBeforeLast("."))
} else {
listOf(it)
}
}
return (referencesFromOperations + referencesFromExpressions + referencesFromKDocs).toSet()
}

private fun rearrangeImports(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
|import org.junit.Test
|import org.cqfn.diktat.Foo
|
|class Example {
|class Example {
|val x: Test = null
|val y: Foo = null
|}
Expand Down Expand Up @@ -273,7 +273,7 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
|
|import org.cqfn.diktat.example.Foo
|
|class Example {
|class Example {
|}
""".trimMargin(),
LintError(1, 1, ruleId, "${Warnings.UNUSED_IMPORT.warnText()} org.cqfn.diktat.example.Foo - unused import", true)
Expand All @@ -289,7 +289,7 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
|
|import org.cqfn.diktat.Foo
|
|class Example {
|class Example {
|}
""".trimMargin(),
LintError(1, 1, ruleId, "${Warnings.UNUSED_IMPORT.warnText()} org.cqfn.diktat.Foo - unused import", true)
Expand All @@ -305,7 +305,7 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
|
|import org.cqfn.diktat.Foo
|
|class Example {
|class Example {
|val x: Foo = null
|}
""".trimMargin(),
Expand All @@ -321,7 +321,7 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
|
|import kotlin.io.path.div
|
|class Example {
|class Example {
|val pom = kotlin.io.path.createTempFile().toFile()
|val x = listOf(pom.parentFile.toPath() / "src/main/kotlin/exclusion")
|}
Expand Down Expand Up @@ -482,4 +482,83 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
LintError(1, 1, ruleId, "${Warnings.UNUSED_IMPORT.warnText()} tasks.getValue - unused import", true)
)
}

@Test
@Tag(WarningNames.UNUSED_IMPORT)
fun `import in KDoc #1`() {
lintMethod(
"""
|import java.io.IOException
|
|interface BluetoothApi {
|
| /**
| * Send array of bytes to bluetooth output stream.
| * This call is asynchronous.
| *
| * Note that this operation can still throw an [IOException] if the remote device silently
| * closes the connection so the pipe gets broken.
| *
| * @param bytes data to send
| * @return true if success, false if there was an error or device has been disconnected
| */
| fun trySend(bytes: ByteArray): Boolean
|}
""".trimMargin(),
)
}

@Test
@Tag(WarningNames.UNUSED_IMPORT)
fun `import in KDoc #2`() {
lintMethod(
"""
|import java.io.IOException
|import java.io.IOException as IOE
|import java.io.UncheckedIOException
|import java.io.UncheckedIOException as UIOE
|
|interface BluetoothApi {
| /**
| * @see IOException
| * @see [UncheckedIOException]
| * @see IOE
| * @see [UIOE]
| */
| fun trySend(bytes: ByteArray): Boolean
|}
""".trimMargin(),
)
}

@Test
@Tag(WarningNames.UNUSED_IMPORT)
fun `import in KDoc #3`() {
lintMethod(
"""
|package com.example
|
|import com.example.Library1 as Lib1
|import com.example.Library1.doSmth as doSmthElse1
|import com.example.Library2 as Lib2
|import com.example.Library2.doSmth as doSmthElse2
|
|object Library1 {
| fun doSmth(): Unit = TODO()
|}
|
|object Library2 {
| fun doSmth(): Unit = TODO()
|}
|
|/**
| * @see Lib1.doSmth
| * @see doSmthElse1
| * @see [Lib2.doSmth]
| * @see [doSmthElse2]
| */
|class Client
""".trimMargin(),
)
}
}

0 comments on commit e1d8cd4

Please sign in to comment.