generated from detekt/detekt-custom-rule-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alexey Panov
committed
Feb 11, 2025
1 parent
2693afe
commit bba4f12
Showing
9 changed files
with
341 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ dependencies { | |
} | ||
|
||
kotlin { | ||
jvmToolchain(8) | ||
jvmToolchain(17) | ||
} | ||
|
||
tasks.withType<Test>().configureEach { | ||
|
81 changes: 81 additions & 0 deletions
81
src/main/kotlin/org/example/detekt/DecomposeComponentContextRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package org.example.detekt | ||
|
||
import io.gitlab.arturbosch.detekt.api.CodeSmell | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Debt | ||
import io.gitlab.arturbosch.detekt.api.Entity | ||
import io.gitlab.arturbosch.detekt.api.Issue | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.Severity | ||
import org.jetbrains.kotlin.psi.KtExpression | ||
import org.jetbrains.kotlin.psi.KtFile | ||
import org.jetbrains.kotlin.psi.KtNamedFunction | ||
import org.jetbrains.kotlin.psi.psiUtil.containingClass | ||
|
||
/** | ||
* A custom Detekt rule to prevent the use of `defaultComponentContext` | ||
* inside Composable functions. | ||
* | ||
* This rule applies only within `Activity` or `Fragment` classes and checks: | ||
* 1. If the file imports `com.arkivanov.decompose.defaultComponentContext`. | ||
* 2. If `defaultComponentContext` is used inside a Composable function. | ||
* 3. If `defaultComponentContext` is used inside `setContent {}`. | ||
*/ | ||
class DecomposeComponentContextRule(config: Config) : Rule(config) { | ||
override val issue = Issue( | ||
id = javaClass.simpleName, | ||
severity = Severity.CodeSmell, | ||
description = "Avoid using defaultComponentContext inside Composable functions.", | ||
debt = Debt(mins = 1) | ||
) | ||
|
||
override fun visitNamedFunction(function: KtNamedFunction) { | ||
super.visitNamedFunction(function) | ||
|
||
// Ensure the function is inside an Activity or Fragment | ||
val ktClass = function.containingClass() ?: return | ||
if (ktClass.isActivity().not() && ktClass.isFragment().not()) return | ||
|
||
// Check if the file imports `defaultComponentContext` | ||
if (function.containingKtFile.hasDefaultComponentContextImport().not()) return | ||
|
||
if (function.isComposableFun()) { | ||
// Directly scan the function body for `defaultComponentContext | ||
function.bodyExpression?.findDefaultComponentContextAndReport() | ||
} else { | ||
// If it's not a Composable, check if `setContent {}` is used | ||
val setContentCall = function.findDescendantCalleeExpression("setContent") | ||
?: return | ||
|
||
// Extract the lambda block inside `setContent` | ||
val lambdaExpression = | ||
setContentCall.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return | ||
|
||
// Scan the lambda block for `defaultComponentContext` | ||
lambdaExpression.bodyExpression?.findDefaultComponentContextAndReport() | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the Kotlin file imports `defaultComponentContext`. | ||
*/ | ||
private fun KtFile.hasDefaultComponentContextImport(): Boolean { | ||
return hasImport("import com.arkivanov.decompose.defaultComponentContext") | ||
} | ||
|
||
/** | ||
* Finds occurrences of `defaultComponentContext` inside an expression | ||
* and reports them as a code smell. | ||
*/ | ||
private fun KtExpression.findDefaultComponentContextAndReport() { | ||
val expression = findDescendantCalleeExpression("defaultComponentContext") ?: return | ||
|
||
report( | ||
CodeSmell( | ||
issue = issue, | ||
entity = Entity.from(expression), | ||
message = "Avoid using defaultComponentContext inside Composable functions." | ||
) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package org.example.detekt | ||
|
||
import org.jetbrains.kotlin.psi.KtCallExpression | ||
import org.jetbrains.kotlin.psi.KtClass | ||
import org.jetbrains.kotlin.psi.KtExpression | ||
import org.jetbrains.kotlin.psi.KtFile | ||
import org.jetbrains.kotlin.psi.KtFunction | ||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType | ||
|
||
/** | ||
* Finds a descendant function call expression with a specific name. | ||
* | ||
* @param text The function name to search for (e.g., "setContent"). | ||
* @return The matching `KtCallExpression`, or `null` if not found. | ||
*/ | ||
internal fun KtExpression.findDescendantCalleeExpression(text: String): KtCallExpression? { | ||
return collectDescendantsOfType<KtCallExpression>() | ||
.find { it.calleeExpression?.text == text } | ||
} | ||
|
||
/** | ||
* Checks if the Kotlin file contains a specific import statement. | ||
* | ||
* @param import The fully qualified import statement to check. | ||
* @return `true` if the import exists, otherwise `false`. | ||
*/ | ||
internal fun KtFile.hasImport(import: String): Boolean { | ||
return importDirectives.any { it.text == import } | ||
} | ||
|
||
/** | ||
* Determines if a function is marked with the `@Composable` annotation. | ||
* | ||
* @return `true` if the function is Composable, otherwise `false`. | ||
*/ | ||
internal fun KtFunction.isComposableFun(): Boolean { | ||
return annotationEntries.any { it.text == "@Composable" } | ||
} | ||
|
||
/** | ||
* Determines if a class extends an `Activity`. | ||
* | ||
* @return `true` if the class is an Activity, otherwise `false`. | ||
*/ | ||
internal fun KtClass.isActivity(): Boolean { | ||
return hasSuperTypeEndingWith("Activity") | ||
} | ||
|
||
/** | ||
* Determines if a class extends a `Fragment`. | ||
* | ||
* @return `true` if the class is a Fragment, otherwise `false`. | ||
*/ | ||
internal fun KtClass.isFragment(): Boolean { | ||
return hasSuperTypeEndingWith("Fragment") | ||
} | ||
|
||
/** | ||
* Checks if the class has a superclass that ends with a given suffix. | ||
* | ||
* This is useful for detecting inheritance from `Activity`, `Fragment`, or any custom base classes. | ||
* | ||
* @param suffix The suffix to check (e.g., "Activity", "Fragment"). | ||
* @return `true` if the class inherits from a matching type, otherwise `false`. | ||
*/ | ||
private fun KtClass.hasSuperTypeEndingWith(suffix: String): Boolean { | ||
return superTypeListEntries | ||
.mapNotNull { it.typeReference?.text } | ||
.any { it.endsWith(suffix) } | ||
} |
2 changes: 1 addition & 1 deletion
2
src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
org.example.detekt.MyRuleSetProvider | ||
org.example.detekt.DecomposeRuleSetProvider |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
MyRuleSet: | ||
MyRule: | ||
DecomposeRuleSetProvider: | ||
DecomposeComponentContextRule: | ||
active: true |
Oops, something went wrong.