From cd73b13dde4ffd359bef613a6b6456a1fcef84fd Mon Sep 17 00:00:00 2001 From: Kirill Gevorkyan <26010098+kgevorkyan@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:38:59 +0300 Subject: [PATCH 1/6] wip ### What's done: * Wip --- .../diktat/ruleset/constants/Warnings.kt | 1 + .../rules/chapter3/PreviewAnnotationRule.kt | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt index e7ffb0228a..9a9e44863c 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt @@ -131,6 +131,7 @@ enum class Warnings( WRONG_WHITESPACE(true, "3.8.1", "incorrect usage of whitespaces for code separation"), TOO_MANY_CONSECUTIVE_SPACES(true, "3.8.1", "too many consecutive spaces"), ANNOTATION_NEW_LINE(true, "3.12.1", "annotations must be on new line"), + PREVIEW_ANNOTATION(false, "3.12.2", "if method has `@Preview` annotation such method should be private and function name should end with `Preview` suffix"), ENUMS_SEPARATED(true, "3.9.1", "enum is incorrectly formatted"), WHEN_WITHOUT_ELSE(true, "3.11.1", "each 'when' statement must have else at the end"), LONG_NUMERICAL_VALUES_SEPARATED(true, "3.14.2", "long numerical values should be separated with underscore"), diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt new file mode 100644 index 0000000000..d6b88bf5bb --- /dev/null +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt @@ -0,0 +1,84 @@ +package com.saveourtool.diktat.ruleset.rules.chapter3 + +import com.saveourtool.diktat.common.config.rules.RulesConfig +import com.saveourtool.diktat.ruleset.constants.Warnings +import com.saveourtool.diktat.ruleset.constants.Warnings.PREVIEW_ANNOTATION +import com.saveourtool.diktat.ruleset.rules.DiktatRule +import com.saveourtool.diktat.ruleset.utils.* + +import org.jetbrains.kotlin.KtNodeTypes.ANNOTATION_ENTRY +import org.jetbrains.kotlin.KtNodeTypes.FUN +import org.jetbrains.kotlin.KtNodeTypes.MODIFIER_LIST +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl + +/** + * This rule checks, whether the method has `@Preview` annotation (Jetpack Compose) + * If so, such method should be private and function name should end with `Preview` suffix + */ +class PreviewAnnotationRule(configRules: List) : DiktatRule( + NAME_ID, + configRules, + listOf(Warnings.PREVIEW_ANNOTATION) +) { + override fun logic(node: ASTNode) { + when (node.elementType) { + FUN -> checkFunctionSignature(node) + else -> return + } + } + + private fun checkFunctionSignature(node: ASTNode) { + node.findChildByType(MODIFIER_LIST)?.let { modList -> + fixAnnotation(modList) + } + } + + private fun fixAnnotation(node: ASTNode) { + if (node.getAllChildrenWithType(ANNOTATION_ENTRY).size <= 1) { + return + } + + node.getAllChildrenWithType(ANNOTATION_ENTRY).forEach { annotationNode -> + if (!annotationNode.isStandardMethod() || !annotationNode.isMethodHasPreviewSuffix()) { + doWarnAndFix(annotationNode) + } + } + } + + private fun doWarnAndFix(node: ASTNode) { + PREVIEW_ANNOTATION.warnAndFix( + configRules, + emitWarn, + isFixMode, + "Method, annotated with ${node.text} should has `Preview` suffix and be private", + node.startOffset, + node + ) { + if (rightSide) { + if (node.treeNext?.isWhiteSpace() == true) { + node.removeChild(node.treeNext) + } + node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) + } + + if (node == node.treeParent.getFirstChildWithType(node.elementType)) { + // Current node is ANNOTATION_ENTRY. treeParent(ModifierList) -> treeParent(PRIMARY_CONSTRUCTOR) + // Checks if there is a white space before grandparent node + val hasSpaceBeforeGrandparent = node + .treeParent + .treeParent + .treePrev + .isWhiteSpace() + if (hasSpaceBeforeGrandparent) { + (node.treeParent.treeParent.treePrev as LeafPsiElement).rawReplaceWithText("\n") + } + } + } + } + + companion object { + const val NAME_ID = "preview-annotation" + } +} From 8bdbf5169a3caee0ebf0fdc5783d564df76e2295 Mon Sep 17 00:00:00 2001 From: Kirill Gevorkyan <26010098+kgevorkyan@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:52:40 +0300 Subject: [PATCH 2/6] wip ### What's done: * Wip --- .../diktat/ruleset/constants/Warnings.kt | 2 +- .../rules/chapter3/PreviewAnnotationRule.kt | 51 +++++++++++-------- .../chapter3/PreviewAnnotationWarnTest.kt | 41 +++++++++++++++ 3 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt index 9a9e44863c..f30154d0d7 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/constants/Warnings.kt @@ -131,7 +131,7 @@ enum class Warnings( WRONG_WHITESPACE(true, "3.8.1", "incorrect usage of whitespaces for code separation"), TOO_MANY_CONSECUTIVE_SPACES(true, "3.8.1", "too many consecutive spaces"), ANNOTATION_NEW_LINE(true, "3.12.1", "annotations must be on new line"), - PREVIEW_ANNOTATION(false, "3.12.2", "if method has `@Preview` annotation such method should be private and function name should end with `Preview` suffix"), + PREVIEW_ANNOTATION(false, "3.12.2", "method, annotated with `@Preview` annotation should be private and has `Preview` suffix"), ENUMS_SEPARATED(true, "3.9.1", "enum is incorrectly formatted"), WHEN_WITHOUT_ELSE(true, "3.11.1", "each 'when' statement must have else at the end"), LONG_NUMERICAL_VALUES_SEPARATED(true, "3.14.2", "long numerical values should be separated with underscore"), diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt index d6b88bf5bb..78006170ca 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt @@ -20,7 +20,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl class PreviewAnnotationRule(configRules: List) : DiktatRule( NAME_ID, configRules, - listOf(Warnings.PREVIEW_ANNOTATION) + listOf(PREVIEW_ANNOTATION) ) { override fun logic(node: ASTNode) { when (node.elementType) { @@ -52,30 +52,39 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( configRules, emitWarn, isFixMode, - "Method, annotated with ${node.text} should has `Preview` suffix and be private", + "${node.treeParent.text} method should has `Preview` suffix", node.startOffset, node ) { - if (rightSide) { - if (node.treeNext?.isWhiteSpace() == true) { - node.removeChild(node.treeNext) - } - node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) - } - - if (node == node.treeParent.getFirstChildWithType(node.elementType)) { - // Current node is ANNOTATION_ENTRY. treeParent(ModifierList) -> treeParent(PRIMARY_CONSTRUCTOR) - // Checks if there is a white space before grandparent node - val hasSpaceBeforeGrandparent = node - .treeParent - .treeParent - .treePrev - .isWhiteSpace() - if (hasSpaceBeforeGrandparent) { - (node.treeParent.treeParent.treePrev as LeafPsiElement).rawReplaceWithText("\n") - } - } + // todo: +// if (rightSide) { +// if (node.treeNext?.isWhiteSpace() == true) { +// node.removeChild(node.treeNext) +// } +// node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) +// } +// +// if (node == node.treeParent.getFirstChildWithType(node.elementType)) { +// // Current node is ANNOTATION_ENTRY. treeParent(ModifierList) -> treeParent(PRIMARY_CONSTRUCTOR) +// // Checks if there is a white space before grandparent node +// val hasSpaceBeforeGrandparent = node +// .treeParent +// .treeParent +// .treePrev +// .isWhiteSpace() +// if (hasSpaceBeforeGrandparent) { +// (node.treeParent.treeParent.treePrev as LeafPsiElement).rawReplaceWithText("\n") +// } +// } } + PREVIEW_ANNOTATION.warnAndFix( + configRules, + emitWarn, + isFixMode, + "${node.treeParent.text} method should be private", + node.startOffset, + node + ) {} } companion object { diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt new file mode 100644 index 0000000000..1b6a4cd4ba --- /dev/null +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt @@ -0,0 +1,41 @@ +package com.saveourtool.diktat.ruleset.chapter3 + +import com.saveourtool.diktat.api.DiktatError +import com.saveourtool.diktat.common.config.rules.DIKTAT_RULE_SET_ID +import com.saveourtool.diktat.ruleset.constants.Warnings +import com.saveourtool.diktat.ruleset.rules.chapter3.PreviewAnnotationRule +import com.saveourtool.diktat.util.LintTestBase +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + + +class PreviewAnnotationWarnTest : LintTestBase(com.saveourtool.diktat.ruleset.rules.chapter3::PreviewAnnotationRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:${PreviewAnnotationRule.NAME_ID}" + + @Test + @Tag(WarningNames.PREVIEW_ANNOTATION) + fun `no warn`() { + lintMethod( + """ + |@Preview + |@Composable + |private fun BannerPreview() {} + """.trimMargin() + ) + } + + + @Test + @Tag(WarningNames.PREVIEW_ANNOTATION) + fun `method is not private`() { + lintMethod( + """ + |@Preview + |@Composable + |fun BannerPreview() {} + """.trimMargin(), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} @SomeAnnotation not on a single line", true), + DiktatError(1, 17, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} @SecondAnnotation not on a single line", true) + ) + } +} From b4cba27f017d50d7175a0fa3bca997f06a90e335 Mon Sep 17 00:00:00 2001 From: Kirill Gevorkyan <26010098+kgevorkyan@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:30:56 +0300 Subject: [PATCH 3/6] WIP ### What's done: * WIP --- .../rules/chapter3/PreviewAnnotationRule.kt | 90 ++++++++----------- .../chapter3/PreviewAnnotationWarnTest.kt | 4 +- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt index 78006170ca..95a60be7d0 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt @@ -1,7 +1,6 @@ package com.saveourtool.diktat.ruleset.rules.chapter3 import com.saveourtool.diktat.common.config.rules.RulesConfig -import com.saveourtool.diktat.ruleset.constants.Warnings import com.saveourtool.diktat.ruleset.constants.Warnings.PREVIEW_ANNOTATION import com.saveourtool.diktat.ruleset.rules.DiktatRule import com.saveourtool.diktat.ruleset.utils.* @@ -10,8 +9,8 @@ import org.jetbrains.kotlin.KtNodeTypes.ANNOTATION_ENTRY import org.jetbrains.kotlin.KtNodeTypes.FUN import org.jetbrains.kotlin.KtNodeTypes.MODIFIER_LIST import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.psiUtil.isPrivate /** * This rule checks, whether the method has `@Preview` annotation (Jetpack Compose) @@ -23,71 +22,60 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( listOf(PREVIEW_ANNOTATION) ) { override fun logic(node: ASTNode) { - when (node.elementType) { - FUN -> checkFunctionSignature(node) - else -> return + if (node.elementType == FUN) { + checkFunctionSignature(node) } } private fun checkFunctionSignature(node: ASTNode) { node.findChildByType(MODIFIER_LIST)?.let { modList -> - fixAnnotation(modList) + doCheck(node, modList) } } - private fun fixAnnotation(node: ASTNode) { - if (node.getAllChildrenWithType(ANNOTATION_ENTRY).size <= 1) { + private fun doCheck(functionNode: ASTNode, modeList: ASTNode) { + if (modeList.getAllChildrenWithType(ANNOTATION_ENTRY).isEmpty()) { return } - node.getAllChildrenWithType(ANNOTATION_ENTRY).forEach { annotationNode -> - if (!annotationNode.isStandardMethod() || !annotationNode.isMethodHasPreviewSuffix()) { - doWarnAndFix(annotationNode) + modeList.getAllChildrenWithType(ANNOTATION_ENTRY).filter { + it.text.contains("$ANNOTATION_SYMBOL$PREVIEW_ANNOTATION_TEXT") + }.forEach { annotationNode -> + if (!((functionNode.psi as KtNamedFunction).isPrivate())) { + PREVIEW_ANNOTATION.warnAndFix( + configRules, + emitWarn, + isFixMode, + "${functionNode.text} method should has `Preview` suffix", + functionNode.startOffset, + functionNode + ) { + // TODO: provide fix + } } - } - } - private fun doWarnAndFix(node: ASTNode) { - PREVIEW_ANNOTATION.warnAndFix( - configRules, - emitWarn, - isFixMode, - "${node.treeParent.text} method should has `Preview` suffix", - node.startOffset, - node - ) { - // todo: -// if (rightSide) { -// if (node.treeNext?.isWhiteSpace() == true) { -// node.removeChild(node.treeNext) -// } -// node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) -// } -// -// if (node == node.treeParent.getFirstChildWithType(node.elementType)) { -// // Current node is ANNOTATION_ENTRY. treeParent(ModifierList) -> treeParent(PRIMARY_CONSTRUCTOR) -// // Checks if there is a white space before grandparent node -// val hasSpaceBeforeGrandparent = node -// .treeParent -// .treeParent -// .treePrev -// .isWhiteSpace() -// if (hasSpaceBeforeGrandparent) { -// (node.treeParent.treeParent.treePrev as LeafPsiElement).rawReplaceWithText("\n") -// } -// } + if(!functionNode.isMethodHasPreviewSuffix()) { + PREVIEW_ANNOTATION.warnAndFix( + configRules, + emitWarn, + isFixMode, + "${functionNode.treeParent.text} method should be private", + functionNode.startOffset, + functionNode + ) { + // TODO: provide fix + } + } } - PREVIEW_ANNOTATION.warnAndFix( - configRules, - emitWarn, - isFixMode, - "${node.treeParent.text} method should be private", - node.startOffset, - node - ) {} } + private fun ASTNode.isMethodHasPreviewSuffix() = + this.getIdentifierName()?.text?.contains(PREVIEW_ANNOTATION_TEXT) ?: false + + companion object { const val NAME_ID = "preview-annotation" + const val ANNOTATION_SYMBOL = "@" + const val PREVIEW_ANNOTATION_TEXT = "Preview" } } diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt index 1b6a4cd4ba..fd6d8ba9ae 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt @@ -5,11 +5,13 @@ import com.saveourtool.diktat.common.config.rules.DIKTAT_RULE_SET_ID import com.saveourtool.diktat.ruleset.constants.Warnings import com.saveourtool.diktat.ruleset.rules.chapter3.PreviewAnnotationRule import com.saveourtool.diktat.util.LintTestBase +import generated.WarningNames +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test -class PreviewAnnotationWarnTest : LintTestBase(com.saveourtool.diktat.ruleset.rules.chapter3::PreviewAnnotationRule) { +class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { private val ruleId = "$DIKTAT_RULE_SET_ID:${PreviewAnnotationRule.NAME_ID}" @Test From f40186e9bcbff15935e80cbea2928866e3edd883 Mon Sep 17 00:00:00 2001 From: Kirill Gevorkyan <26010098+kgevorkyan@users.noreply.github.com> Date: Thu, 31 Aug 2023 17:14:06 +0300 Subject: [PATCH 4/6] WIP ### What's done: * WIP --- .../rules/chapter3/PreviewAnnotationRule.kt | 24 ++++++++++++------- .../chapter3/PreviewAnnotationWarnTest.kt | 4 +--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt index 95a60be7d0..0e5a7da0ff 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt @@ -3,7 +3,8 @@ package com.saveourtool.diktat.ruleset.rules.chapter3 import com.saveourtool.diktat.common.config.rules.RulesConfig import com.saveourtool.diktat.ruleset.constants.Warnings.PREVIEW_ANNOTATION import com.saveourtool.diktat.ruleset.rules.DiktatRule -import com.saveourtool.diktat.ruleset.utils.* +import com.saveourtool.diktat.ruleset.utils.getAllChildrenWithType +import com.saveourtool.diktat.ruleset.utils.getIdentifierName import org.jetbrains.kotlin.KtNodeTypes.ANNOTATION_ENTRY import org.jetbrains.kotlin.KtNodeTypes.FUN @@ -38,15 +39,20 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( return } - modeList.getAllChildrenWithType(ANNOTATION_ENTRY).filter { + val previewAnnotationNode = modeList.getAllChildrenWithType(ANNOTATION_ENTRY).firstOrNull { it.text.contains("$ANNOTATION_SYMBOL$PREVIEW_ANNOTATION_TEXT") - }.forEach { annotationNode -> + } + + previewAnnotationNode?.let { + val functionName = functionNode.getIdentifierName()!!.text + + // warn if function is not private if (!((functionNode.psi as KtNamedFunction).isPrivate())) { PREVIEW_ANNOTATION.warnAndFix( configRules, emitWarn, isFixMode, - "${functionNode.text} method should has `Preview` suffix", + "$functionName method should be private", functionNode.startOffset, functionNode ) { @@ -54,12 +60,13 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( } } - if(!functionNode.isMethodHasPreviewSuffix()) { + // warn if function has no `Preview` suffix + if (!isMethodHasPreviewSuffix(functionName)) { PREVIEW_ANNOTATION.warnAndFix( configRules, emitWarn, isFixMode, - "${functionNode.treeParent.text} method should be private", + "$functionName method should has `Preview` suffix", functionNode.startOffset, functionNode ) { @@ -69,8 +76,9 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( } } - private fun ASTNode.isMethodHasPreviewSuffix() = - this.getIdentifierName()?.text?.contains(PREVIEW_ANNOTATION_TEXT) ?: false + + private fun isMethodHasPreviewSuffix(functionName: String) = + functionName.contains(PREVIEW_ANNOTATION_TEXT) companion object { diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt index fd6d8ba9ae..fe6e6d2dee 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt @@ -6,7 +6,6 @@ import com.saveourtool.diktat.ruleset.constants.Warnings import com.saveourtool.diktat.ruleset.rules.chapter3.PreviewAnnotationRule import com.saveourtool.diktat.util.LintTestBase import generated.WarningNames -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test @@ -36,8 +35,7 @@ class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { |@Composable |fun BannerPreview() {} """.trimMargin(), - DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} @SomeAnnotation not on a single line", true), - DiktatError(1, 17, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} @SecondAnnotation not on a single line", true) + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} BannerPreview method should be private", false), ) } } From eab9a0294c1226b4664badee72f0e73a301effa7 Mon Sep 17 00:00:00 2001 From: Kirill Gevorkyan <26010098+kgevorkyan@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:04:23 +0300 Subject: [PATCH 5/6] WIP ### What's done: * WIP --- diktat-analysis.yml | 3 +++ .../diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt | 2 ++ .../diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt | 6 +++--- diktat-rules/src/main/resources/diktat-analysis-huawei.yml | 3 +++ diktat-rules/src/main/resources/diktat-analysis.yml | 3 +++ info/available-rules.md | 1 + 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/diktat-analysis.yml b/diktat-analysis.yml index ab75eaf7d0..523c1d222b 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -300,6 +300,9 @@ # Checks that annotation is on a single line - name: ANNOTATION_NEW_LINE enabled: true +# Checks that method annotated with `Preview` annotation is private and has Preview suffix +- name: PREVIEW_ANNOTATION + enabled: true # Checks that enum structure is correct: enum entries should be separated by comma and line break and last entry should have semicolon in the end. - name: ENUMS_SEPARATED enabled: true diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt index 61d9db3357..de9c4ae99e 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt @@ -27,6 +27,7 @@ import com.saveourtool.diktat.ruleset.rules.chapter3.LongNumericalValuesSeparate import com.saveourtool.diktat.ruleset.rules.chapter3.MagicNumberRule import com.saveourtool.diktat.ruleset.rules.chapter3.MultipleModifiersSequence import com.saveourtool.diktat.ruleset.rules.chapter3.NullableTypeRule +import com.saveourtool.diktat.ruleset.rules.chapter3.PreviewAnnotationRule import com.saveourtool.diktat.ruleset.rules.chapter3.RangeConventionalRule import com.saveourtool.diktat.ruleset.rules.chapter3.SingleLineStatementsRule import com.saveourtool.diktat.ruleset.rules.chapter3.SortRule @@ -159,6 +160,7 @@ class DiktatRuleSetFactoryImpl : DiktatRuleSetFactory { ::LongNumericalValuesSeparatedRule, ::NestedFunctionBlock, ::AnnotationNewLineRule, + ::PreviewAnnotationRule, ::SortRule, ::EnumsSeparated, ::StringConcatenationRule, diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt index 0e5a7da0ff..6efab55be8 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt @@ -44,7 +44,7 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( } previewAnnotationNode?.let { - val functionName = functionNode.getIdentifierName()!!.text + val functionName = functionNode.getIdentifierName()?.text ?: return // warn if function is not private if (!((functionNode.psi as KtNamedFunction).isPrivate())) { @@ -56,7 +56,7 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( functionNode.startOffset, functionNode ) { - // TODO: provide fix + // provide fix } } @@ -70,7 +70,7 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( functionNode.startOffset, functionNode ) { - // TODO: provide fix + // provide fix } } } diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 1c481bc3c9..d7f8718fed 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -299,6 +299,9 @@ # Checks that annotation is on a single line - name: ANNOTATION_NEW_LINE enabled: true +# Checks that method annotated with `Preview` annotation is private and has Preview suffix +- name: PREVIEW_ANNOTATION + enabled: true # Checks that enum structure is correct: enum entries should be separated by comma and line break and last entry should have semicolon in the end. - name: ENUMS_SEPARATED enabled: true diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml index 7bd0bd3ab9..2f276637c6 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -299,6 +299,9 @@ # Checks that annotation is on a single line - name: ANNOTATION_NEW_LINE enabled: true +# Checks that method annotated with `Preview` annotation is private and has Preview suffix +- name: PREVIEW_ANNOTATION + enabled: true # Checks that enum structure is correct: enum entries should be separated by comma and line break and last entry should have semicolon in the end. - name: ENUMS_SEPARATED enabled: true diff --git a/info/available-rules.md b/info/available-rules.md index b6fd059de9..1b2fce484a 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -83,6 +83,7 @@ | 3 | 3.10.2 | LOCAL_VARIABLE_EARLY_DECLARATION | Check: warns if a local variable is declared not immediately before its usage.
Fix (not implemented yet): moves the variable declaration. | no | no | add auto fix | | 3 | 3.11.1 | WHEN_WITHOUT_ELSE | Check: warns if a `when` statement does not have `else` in the end.
Fix: adds `else` when a statement doesn't have it. | yes | no | - | If a `when` statement of the enum or sealed type contains all values of the enum, there is no need to have the "else" branch. | | 3 | 3.12.1 | ANNOTATION_NEW_LINE | Check: warns if an annotation is not on a new single line. | yes | no | - | +| 3 | 3.12.2 | PREVIEW_ANNOTATION | Check: warns if method, annotated with `@Preview` is not private or has no `Preview` suffix. | yes | no | - | | 3 | 3.14.1 | WRONG_MULTIPLE_MODIFIERS_ORDER | Check: warns if the multiple modifiers in the sequence are in the wrong order. Value identifier supported in Kotlin 1.5 | yes | no | - | | 3 | 3.14.2 | LONG_NUMERICAL_VALUES_SEPARATED | Check: warns if the value of the integer or float constant is too big. | no | maxNumberLength maxBlockLength | - | | 3 | 3.14.3 | MAGIC_NUMBER | Check: warns if there are magic numbers in the code. | no | ignoreNumbers, ignoreHashCodeFunction, ignorePropertyDeclaration, ignoreLocalVariableDeclaration, ignoreConstantDeclaration, ignoreCompanionObjectPropertyDeclaration, ignoreEnums, ignoreRanges, ignoreExtensionFunctions | no | From 491724e82cdaac4b311b7580cd4103bf77de534d Mon Sep 17 00:00:00 2001 From: Kirill Gevorkyan <26010098+kgevorkyan@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:54:26 +0300 Subject: [PATCH 6/6] WIP ### What's done: * WIP --- .../rules/chapter3/PreviewAnnotationRule.kt | 4 +-- .../chapter3/PreviewAnnotationWarnTest.kt | 28 ++++++++++++++++++- info/guide/guide-chapter-3.md | 13 ++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt index 6efab55be8..ebd648a105 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/PreviewAnnotationRule.kt @@ -76,14 +76,12 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( } } - private fun isMethodHasPreviewSuffix(functionName: String) = functionName.contains(PREVIEW_ANNOTATION_TEXT) - companion object { - const val NAME_ID = "preview-annotation" const val ANNOTATION_SYMBOL = "@" + const val NAME_ID = "preview-annotation" const val PREVIEW_ANNOTATION_TEXT = "Preview" } } diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt index fe6e6d2dee..817182217b 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationWarnTest.kt @@ -25,7 +25,6 @@ class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { ) } - @Test @Tag(WarningNames.PREVIEW_ANNOTATION) fun `method is not private`() { @@ -38,4 +37,31 @@ class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} BannerPreview method should be private", false), ) } + + @Test + @Tag(WarningNames.PREVIEW_ANNOTATION) + fun `method has no preview suffix`() { + lintMethod( + """ + |@Preview + |@Composable + |private fun Banner() {} + """.trimMargin(), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should has `Preview` suffix", false), + ) + } + + @Test + @Tag(WarningNames.PREVIEW_ANNOTATION) + fun `method has no preview suffix and is not private`() { + lintMethod( + """ + |@Preview + |@Composable + |fun Banner() {} + """.trimMargin(), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should be private", false), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should has `Preview` suffix", false), + ) + } } diff --git a/info/guide/guide-chapter-3.md b/info/guide/guide-chapter-3.md index 5ee40294ca..8a5ed099e7 100644 --- a/info/guide/guide-chapter-3.md +++ b/info/guide/guide-chapter-3.md @@ -964,7 +964,8 @@ The compiler can issue a warning when it is missing. ### 3.12 Annotations - +This section contains recommendations regarding annotations. +#### 3.12.1 Whitespaces and newlines for annotations Each annotation applied to a class, method or constructor should be placed on its own line. Consider the following examples: 1. Annotations applied to the class, method or constructor are placed on separate lines (one annotation per line). @@ -988,6 +989,16 @@ fun getNameIfPresent() { /* ... */ } ```kotlin @MustBeDocumented @CustomAnnotation val loader: DataLoader ``` +#### 3.12.2 Preview annotation +`@Preview` (Jetpack Compose) functions should end with 'Preview' suffix and are also be private + +**Valid example**: +```kotlin +@Preview +@Composable +private fun BannerPreview() {} +``` + ### 3.13 Block comments