Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for UNUSED_IMPORT in KDoc #1368

Merged
merged 4 commits into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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 +74,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 +226,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 +264,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 @@ -33,7 +33,6 @@ import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_END
import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_START
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.OPEN_QUOTE
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.RBRACKET
import com.pinterest.ktlint.core.ast.ElementType.RPAR
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(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import org.cqfn.diktat.ruleset.rules.chapter5.AvoidNestedFunctionsRule
import org.cqfn.diktat.util.LintTestBase

import com.pinterest.ktlint.core.LintError
import generated.WarningNames
import generated.WarningNames.AVOID_NESTED_FUNCTIONS
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.Warnings
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.ruleset.rules.chapter5.ParameterNameInOuterLambdaRule
import org.cqfn.diktat.ruleset.rules.chapter6.classes.AbstractClassesRule
import org.cqfn.diktat.util.LintTestBase

import com.pinterest.ktlint.core.LintError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.api.FeatureInAlphaState
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.ElementType.BLOCK
import com.pinterest.ktlint.core.ast.ElementType.CLASS
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
Expand All @@ -23,7 +22,6 @@ import com.pinterest.ktlint.core.ast.ElementType.FILE
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
import com.pinterest.ktlint.core.ast.ElementType.INTEGER_CONSTANT
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE
Expand Down Expand Up @@ -129,7 +127,7 @@ class AstNodeUtilsTest {
fun `test getTypeParameterList`() {
val code = """
class Array<T>(val size: Int) {

}
""".trimIndent()
applyToCode(code, 1) { node, counter ->
Expand Down Expand Up @@ -408,7 +406,7 @@ class AstNodeUtilsTest {
fun `test isNodeFromFileLevel - node isn't from file level`() {
val code = """
val x = 2

""".trimIndent()
applyToCode(code, 8) { node, counter ->
if (node.elementType != FILE) {
Expand Down