Skip to content

Commit

Permalink
Provide fix logic for @Preview functions (#1728)
Browse files Browse the repository at this point in the history
### What's done:
* Provide fix logic
* Add tests
  • Loading branch information
kgevorkyan authored Sep 5, 2023
1 parent eb65634 commit fbe1146
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ 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

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

Expand All @@ -34,6 +43,7 @@ class PreviewAnnotationRule(configRules: List<RulesConfig>) : DiktatRule(
}
}

@Suppress("TOO_LONG_FUNCTION")
private fun doCheck(functionNode: ASTNode, modeList: ASTNode) {
if (modeList.getAllChildrenWithType(ANNOTATION_ENTRY).isEmpty()) {
return
Expand All @@ -44,7 +54,8 @@ class PreviewAnnotationRule(configRules: List<RulesConfig>) : 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())) {
Expand All @@ -56,7 +67,7 @@ class PreviewAnnotationRule(configRules: List<RulesConfig>) : DiktatRule(
functionNode.startOffset,
functionNode
) {
// provide fix
addPrivateModifier(functionNode)
}
}

Expand All @@ -70,7 +81,10 @@ class PreviewAnnotationRule(configRules: List<RulesConfig>) : DiktatRule(
functionNode.startOffset,
functionNode
) {
// provide fix
functionNode.replaceChild(
functionNameNode,
KotlinParser().createNode("${functionNameNode.text}Preview")
)
}
}
}
Expand All @@ -79,6 +93,45 @@ class PreviewAnnotationRule(configRules: List<RulesConfig>) : 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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
}

Expand All @@ -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),
)
}

Expand All @@ -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),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package test.paragraph3.preview_annotation

@Preview
private fun BannerPreview() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package test.paragraph3.preview_annotation

@Preview
private fun Banner() {}
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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() {}

0 comments on commit fbe1146

Please sign in to comment.