From ab404b2e4fa98fb989b626a41ec97b664db57093 Mon Sep 17 00:00:00 2001 From: Pleshkova Daria Date: Wed, 13 Dec 2023 15:53:37 +0300 Subject: [PATCH] ### What's done: - Fixed `KDOC_WITHOUT_THROWS_TAG` rule when it adds a @throws annotation to the function, which has `throw` inside try-catch block Closes #1718 --- .../rules/chapter2/kdoc/KdocMethods.kt | 30 +++++++++++++++++++ .../ruleset/chapter2/KdocMethodsFixTest.kt | 6 ++++ .../ruleset/chapter2/KdocMethodsTest.kt | 20 ------------- .../methods/KdocWithoutThrowsTagExpected.kt | 17 +++++++++++ .../methods/KdocWithoutThrowsTagTested.kt | 13 ++++++++ 5 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagExpected.kt create mode 100644 diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagTested.kt diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt index 3e8d96494e..feb26df189 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt @@ -12,6 +12,7 @@ import com.saveourtool.diktat.ruleset.utils.KotlinParser import com.saveourtool.diktat.ruleset.utils.appendNewlineMergingWhiteSpace import com.saveourtool.diktat.ruleset.utils.findAllDescendantsWithSpecificType import com.saveourtool.diktat.ruleset.utils.findChildAfter +import com.saveourtool.diktat.ruleset.utils.getAllChildrenWithType import com.saveourtool.diktat.ruleset.utils.getBodyLines import com.saveourtool.diktat.ruleset.utils.getFilePath import com.saveourtool.diktat.ruleset.utils.getFirstChildWithType @@ -40,6 +41,7 @@ import org.jetbrains.kotlin.KtNodeTypes.MODIFIER_LIST import org.jetbrains.kotlin.KtNodeTypes.REFERENCE_EXPRESSION import org.jetbrains.kotlin.KtNodeTypes.THIS_EXPRESSION import org.jetbrains.kotlin.KtNodeTypes.THROW +import org.jetbrains.kotlin.KtNodeTypes.TRY import org.jetbrains.kotlin.KtNodeTypes.TYPE_REFERENCE import org.jetbrains.kotlin.com.intellij.lang.ASTFactory import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -54,6 +56,7 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.COLON import org.jetbrains.kotlin.lexer.KtTokens.EQ +import org.jetbrains.kotlin.lexer.KtTokens.IDENTIFIER import org.jetbrains.kotlin.lexer.KtTokens.WHITE_SPACE import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtCatchClause @@ -197,11 +200,38 @@ class KdocMethods(configRules: List) : DiktatRule( && !hasReturnKdoc && !isReferenceExpressionWithSameName } + private fun isThrowInTryCatchBlock(node: ASTNode?): Boolean { + node ?: return false + var parent = node.treeParent + while (parent != null && parent.elementType != TRY) { + parent = parent.treeParent + } + val nodeNameList = node.findAllDescendantsWithSpecificType(IDENTIFIER) + val nodeName = if (nodeNameList.isNotEmpty()) { + nodeNameList[0] + } else { + null + } + + if (parent?.elementType == TRY) { + val catchNodes = parent.getAllChildrenWithType(CATCH) + val findName = catchNodes.find { catchNode -> + val catchNodeNames = catchNode.findAllDescendantsWithSpecificType(IDENTIFIER) + val matchingName = catchNodeNames.find { catchNodeName -> catchNodeName.text == nodeName?.text } + matchingName != null + } + return findName != null + } + return false + } + private fun getExplicitlyThrownExceptions(node: ASTNode): Set { val codeBlock = node.getFirstChildWithType(BLOCK) val throwKeywords = codeBlock?.findAllDescendantsWithSpecificType(THROW) + return throwKeywords ?.asSequence() + ?.filter { !isThrowInTryCatchBlock(it) } ?.map { it.psi as KtThrowExpression } ?.filter { // we only take freshly created exceptions here: `throw IAE("stuff")` vs `throw e` diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsFixTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsFixTest.kt index d5fade6004..4627cbf61c 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsFixTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsFixTest.kt @@ -61,4 +61,10 @@ class KdocMethodsFixTest : FixTestBase("test/paragraph2/kdoc/package/src/main/ko fun `KdocMethods rule should reformat code (full example)`() { fixAndCompare("KdocMethodsFullExpected.kt", "KdocMethodsFullTested.kt") } + + @Test + @Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG) + fun `@throws tag shouldn't be added`() { + fixAndCompare("KdocWithoutThrowsTagExpected.kt", "KdocWithoutThrowsTagTested.kt") + } } diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsTest.kt index 38cc753fc2..94c439e65d 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter2/KdocMethodsTest.kt @@ -272,26 +272,6 @@ class KdocMethodsTest : LintTestBase(::KdocMethods) { ) } - @Test - @Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG) - fun `shouldn't add @throw if exception is in catch-block`() { - lintMethod( - """ - |fun parseInputNumber(onSuccess: (number: Int) -> Unit, onFailure: () -> Unit) { - | try { - | val input: Int = binding.inputEditText.text.toString().toInt() - | if (input < 0) - | throw NumberFormatException() - | - | onSuccess(input) - | } catch (e: NumberFormatException) { - | onFailure() - | } - |} - """.trimMargin() - ) - } - @Test @Tag(WarningNames.MISSING_KDOC_TOP_LEVEL) fun `do not force documentation on standard methods`() { diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagExpected.kt new file mode 100644 index 0000000000..fda6f29bcb --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagExpected.kt @@ -0,0 +1,17 @@ +package test.paragraph2.kdoc.`package`.src.main.kotlin.com.saveourtool.diktat.kdoc.methods + +/** + * @param onSuccess + * @param onFailure + */ +fun parseInputNumber(onSuccess: (number: Int) -> Unit, onFailure: () -> Unit) { + try { + val input: Int = binding.inputEditText.text.toString().toInt() + if (input < 0) + throw NumberFormatException() + + onSuccess(input) + } catch (e: NumberFormatException) { + onFailure() + } +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagTested.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagTested.kt new file mode 100644 index 0000000000..8c960770b8 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/package/src/main/kotlin/com/saveourtool/diktat/kdoc/methods/KdocWithoutThrowsTagTested.kt @@ -0,0 +1,13 @@ +package test.paragraph2.kdoc.`package`.src.main.kotlin.com.saveourtool.diktat.kdoc.methods + +fun parseInputNumber(onSuccess: (number: Int) -> Unit, onFailure: () -> Unit) { + try { + val input: Int = binding.inputEditText.text.toString().toInt() + if (input < 0) + throw NumberFormatException() + + onSuccess(input) + } catch (e: NumberFormatException) { + onFailure() + } +}