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 f30154d0d7..357479c215 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", "method, annotated with `@Preview` annotation should be private and has `Preview` suffix"), + PREVIEW_ANNOTATION(true, "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 ebd648a105..c756ab5d69 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,6 +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.KotlinParser +import com.saveourtool.diktat.ruleset.utils.findAllNodesWithCondition import com.saveourtool.diktat.ruleset.utils.getAllChildrenWithType import com.saveourtool.diktat.ruleset.utils.getIdentifierName @@ -10,6 +12,13 @@ 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.PsiWhiteSpaceImpl +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD +import org.jetbrains.kotlin.lexer.KtTokens.INTERNAL_KEYWORD +import org.jetbrains.kotlin.lexer.KtTokens.OPEN_KEYWORD +import org.jetbrains.kotlin.lexer.KtTokens.PROTECTED_KEYWORD +import org.jetbrains.kotlin.lexer.KtTokens.PUBLIC_KEYWORD import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.psiUtil.isPrivate @@ -34,6 +43,7 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( } } + @Suppress("TOO_LONG_FUNCTION") private fun doCheck(functionNode: ASTNode, modeList: ASTNode) { if (modeList.getAllChildrenWithType(ANNOTATION_ENTRY).isEmpty()) { return @@ -44,7 +54,8 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( } previewAnnotationNode?.let { - val functionName = functionNode.getIdentifierName()?.text ?: return + val functionNameNode = functionNode.getIdentifierName() + val functionName = functionNameNode?.text ?: return // warn if function is not private if (!((functionNode.psi as KtNamedFunction).isPrivate())) { @@ -56,7 +67,7 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( functionNode.startOffset, functionNode ) { - // provide fix + addPrivateModifier(functionNode) } } @@ -70,7 +81,10 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( functionNode.startOffset, functionNode ) { - // provide fix + functionNode.replaceChild( + functionNameNode, + KotlinParser().createNode("${functionNameNode.text}Preview") + ) } } } @@ -79,6 +93,45 @@ class PreviewAnnotationRule(configRules: List) : DiktatRule( private fun isMethodHasPreviewSuffix(functionName: String) = functionName.contains(PREVIEW_ANNOTATION_TEXT) + private fun addPrivateModifier(functionNode: ASTNode) { + val modifiersList = functionNode + .findChildByType(MODIFIER_LIST) + ?.getChildren(KtTokens.MODIFIER_KEYWORDS) + ?.toList() + + val isMethodAbstract = modifiersList?.any { + it.elementType == ABSTRACT_KEYWORD + } + + // private modifier is not applicable for abstract methods + if (isMethodAbstract == true) { + return + } + + // these modifiers could be safely replaced via `private` + val modifierForReplacement = modifiersList?.firstOrNull { + it.elementType in listOf( + PUBLIC_KEYWORD, PROTECTED_KEYWORD, INTERNAL_KEYWORD, OPEN_KEYWORD + ) + } + + modifierForReplacement?.let { + // replace current modifier with `private` + val parent = it.treeParent + parent.replaceChild(it, createPrivateModifierNode() + ) + } ?: run { + // the case, when there is no explicit modifier, i.e. `fun foo` + // just add `private` before function identifier `fun` + val funNode = functionNode.findAllNodesWithCondition { it.text == "fun" }.single() + // add `private ` nodes before `fun` + funNode.treeParent?.addChild(PsiWhiteSpaceImpl(" "), funNode) + funNode.treeParent?.addChild(createPrivateModifierNode(), funNode.treePrev) + } + } + + private fun createPrivateModifierNode() = KotlinParser().createNode("private") + companion object { const val ANNOTATION_SYMBOL = "@" const val NAME_ID = "preview-annotation" diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationFixTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationFixTest.kt new file mode 100644 index 0000000000..838683d856 --- /dev/null +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/chapter3/PreviewAnnotationFixTest.kt @@ -0,0 +1,22 @@ +package com.saveourtool.diktat.ruleset.chapter3 + +import com.saveourtool.diktat.ruleset.rules.chapter3.PreviewAnnotationRule +import com.saveourtool.diktat.util.FixTestBase + +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class PreviewAnnotationFixTest : FixTestBase("test/paragraph3/preview_annotation", ::PreviewAnnotationRule) { + @Test + @Tag(WarningNames.PREVIEW_ANNOTATION) + fun `should add private modifier`() { + fixAndCompare("PreviewAnnotationPrivateModifierExpected.kt", "PreviewAnnotationPrivateModifierTest.kt") + } + + @Test + @Tag(WarningNames.PREVIEW_ANNOTATION) + fun `should add Preview suffix`() { + fixAndCompare("PreviewAnnotationMethodNameExpected.kt", "PreviewAnnotationMethodNameTest.kt") + } +} 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 817182217b..46a6a77365 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 @@ -34,7 +34,7 @@ class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { |@Composable |fun BannerPreview() {} """.trimMargin(), - DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} BannerPreview method should be private", false), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} BannerPreview method should be private", true), ) } @@ -47,7 +47,7 @@ class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { |@Composable |private fun Banner() {} """.trimMargin(), - DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should has `Preview` suffix", false), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should has `Preview` suffix", true), ) } @@ -60,8 +60,8 @@ class PreviewAnnotationWarnTest : LintTestBase(::PreviewAnnotationRule) { |@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), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should be private", true), + DiktatError(1, 1, ruleId, "${Warnings.PREVIEW_ANNOTATION.warnText()} Banner method should has `Preview` suffix", true), ) } } diff --git a/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationMethodNameExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationMethodNameExpected.kt new file mode 100644 index 0000000000..cf6cbe65e5 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationMethodNameExpected.kt @@ -0,0 +1,4 @@ +package test.paragraph3.preview_annotation + +@Preview +private fun BannerPreview() {} diff --git a/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationMethodNameTest.kt b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationMethodNameTest.kt new file mode 100644 index 0000000000..491afee34c --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationMethodNameTest.kt @@ -0,0 +1,4 @@ +package test.paragraph3.preview_annotation + +@Preview +private fun Banner() {} diff --git a/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationPrivateModifierExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationPrivateModifierExpected.kt new file mode 100644 index 0000000000..6752cf3b7b --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationPrivateModifierExpected.kt @@ -0,0 +1,20 @@ +package test.paragraph3.preview_annotation + +@Preview +@Composable +private fun BannerPreview1() {} + +@Preview +private fun BannerPreview2() {} + +@Preview +private fun BannerPreview3() {} + +@Preview +private fun BannerPreview4() {} + +@Preview +private fun BannerPreview5() {} + +@Preview +final private fun BannerPreview6() {} diff --git a/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationPrivateModifierTest.kt b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationPrivateModifierTest.kt new file mode 100644 index 0000000000..958186b24b --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/preview_annotation/PreviewAnnotationPrivateModifierTest.kt @@ -0,0 +1,20 @@ +package test.paragraph3.preview_annotation + +@Preview +@Composable +public fun BannerPreview1() {} + +@Preview +protected fun BannerPreview2() {} + +@Preview +internal fun BannerPreview3() {} + +@Preview +fun BannerPreview4() {} + +@Preview +open fun BannerPreview5() {} + +@Preview +final fun BannerPreview6() {}