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

New rule to check if @Preview (Jetpack Compose) functions end with 'Preview' suffix and are also private. Part 1 #1726

Merged
merged 6 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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,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", "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
@@ -0,0 +1,89 @@
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.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.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.isPrivate

/**
* 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<RulesConfig>) : DiktatRule(
NAME_ID,
configRules,
listOf(PREVIEW_ANNOTATION)
) {
override fun logic(node: ASTNode) {
if (node.elementType == FUN) {
checkFunctionSignature(node)
}
}

private fun checkFunctionSignature(node: ASTNode) {
node.findChildByType(MODIFIER_LIST)?.let { modList ->
doCheck(node, modList)
}
}

private fun doCheck(functionNode: ASTNode, modeList: ASTNode) {
if (modeList.getAllChildrenWithType(ANNOTATION_ENTRY).isEmpty()) {
return
}

val previewAnnotationNode = modeList.getAllChildrenWithType(ANNOTATION_ENTRY).firstOrNull {
it.text.contains("$ANNOTATION_SYMBOL$PREVIEW_ANNOTATION_TEXT")
}

previewAnnotationNode?.let {
val functionName = functionNode.getIdentifierName()!!.text
Fixed Show fixed Hide fixed

// warn if function is not private
if (!((functionNode.psi as KtNamedFunction).isPrivate())) {
PREVIEW_ANNOTATION.warnAndFix(
configRules,
emitWarn,
isFixMode,
"$functionName method should be private",
functionNode.startOffset,
functionNode
) {
// TODO: provide fix
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
}
}

// warn if function has no `Preview` suffix
if (!isMethodHasPreviewSuffix(functionName)) {
PREVIEW_ANNOTATION.warnAndFix(
configRules,
emitWarn,
isFixMode,
"$functionName method should has `Preview` suffix",
functionNode.startOffset,
functionNode
) {
// TODO: provide fix
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
}
}
}
}


private fun isMethodHasPreviewSuffix(functionName: String) =
functionName.contains(PREVIEW_ANNOTATION_TEXT)


companion object {
const val NAME_ID = "preview-annotation"
const val ANNOTATION_SYMBOL = "@"
const val PREVIEW_ANNOTATION_TEXT = "Preview"
}
}
Original file line number Diff line number Diff line change
@@ -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 generated.WarningNames
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test


class PreviewAnnotationWarnTest : LintTestBase(::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()} BannerPreview method should be private", false),
)
}
}