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

Provide fix logic for @Preview functions #1728

Merged
merged 8 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -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,46 @@ 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 isContainOpenOrAbstractKeyword = modifiersList?.any {
it.elementType in listOf(OPEN_KEYWORD, ABSTRACT_KEYWORD)
}

// private modifier is not applicable for abstract and open methods
// so search only those, which can be replaced via `private`
if (isContainOpenOrAbstractKeyword == true) {
return
}

// these modifiers could be safely replaced via `private`
val modifierForReplacement = modifiersList?.firstOrNull {
it.elementType in listOf(
PUBLIC_KEYWORD, PROTECTED_KEYWORD, INTERNAL_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
open fun BannerPreview5() {}
nulls marked this conversation as resolved.
Show resolved Hide resolved

@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() {}