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

Add missing modifier rule #77

Open
wants to merge 1 commit into
base: feature/add-access-modifiers-rules
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,58 @@
package com.urlaunched.android.ktlintrules

import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision
import com.pinterest.ktlint.rule.engine.core.api.ElementType
import com.pinterest.ktlint.rule.engine.core.api.Rule
import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtNamedFunction
import utils.CustomRulesUtils.getPackageName
import utils.CustomRulesUtils.isComposable
import utils.CustomRulesUtils.isPreview
import utils.CustomRulesUtils.modifierParameter

class ComposableModifierMissingRule :
Rule(
RuleId(CUSTOM_RULE_ID),
about = About()
),
RuleAutocorrectApproveHandler {
override fun beforeVisitChildNodes(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision
) {
if (node.elementType == ElementType.FUN) {
val function = node.psi as? KtNamedFunction ?: return
if (!function.isComposable ||
function.isPreview ||
function.receiverTypeReference?.text == MODIFIER_TYPE_REFERENCE ||
function.getPackageName()?.contains(DESIGN_RESOURCES_PACKAGE) == true ||
modifierMissingExceptions.any { exception ->
function.name?.contains(exception) == true
}
) {
return
} else {
if (function.modifierParameter() != null) {
return
} else {
emit(
node.startOffset,
MODIFIER_MISSING_ERROR,
false
)
}
}
}
}

companion object {
private const val CUSTOM_RULE_ID = "ktlintrules:composable-modifier-missing-rule"
private const val MODIFIER_MISSING_ERROR =
" This @Composable function emits content but doesn't have a modifier parameter."
private const val DESIGN_RESOURCES_PACKAGE = "core.designsystem.resources"
private val modifierMissingExceptions = listOf("Screen", "Route", "SideEffects")
private const val MODIFIER_TYPE_REFERENCE = "Modifier"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class RuleSetProvider : RuleSetProviderV3(RuleSetId(CUSTOM_RULE_SET_ID)) {
override fun getRuleProviders(): Set<RuleProvider> = setOf(
RuleProvider { ForbiddenImportsRule() },
RuleProvider { ComposableAccessModifiersRule() },
RuleProvider { DataAccessModifierRule() }
RuleProvider { DataAccessModifierRule() },
RuleProvider { ComposableModifierMissingRule() }
)
}
17 changes: 17 additions & 0 deletions ktlintrules/src/main/java/utils/CustomRulesUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package utils

import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType

Expand Down Expand Up @@ -37,4 +40,18 @@ object CustomRulesUtils {

val KtNamedFunction.isComposable: Boolean
get() = annotationEntries.any { it.calleeExpression?.text == "Composable" }

fun KtFunction.modifierParameter(): KtParameter? {
val modifiers = valueParameters.filter { it.isModifier() }
return modifiers.firstOrNull { it.name == "modifier" } ?: modifiers.firstOrNull()
}

private fun KtCallableDeclaration.isModifier(): Boolean =
typeReference?.text?.contains("Modifier") ?: false

fun KtFunction.getPackageName(): String? {
val containingFile = this.containingKtFile
val packageDirective = containingFile.packageDirective
return packageDirective?.fqName?.asString()
}
}