forked from twitter/compose-rules
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script to create all files necessary for a new rule (#352)
- Loading branch information
Showing
6 changed files
with
256 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,132 @@ | ||
#!/usr/bin/env kotlin | ||
|
||
@file:DependsOn("org.apache.velocity:velocity-engine-core:2.4") | ||
|
||
import org.apache.velocity.VelocityContext | ||
import org.apache.velocity.app.VelocityEngine | ||
import org.apache.velocity.runtime.RuntimeConstants | ||
import java.io.File | ||
import java.io.StringWriter | ||
import java.util.* | ||
import kotlin.system.exitProcess | ||
|
||
fun printUsage() { | ||
println("Usage: create-rule [RuleName]") | ||
println() | ||
} | ||
|
||
private val humps by lazy { "(?<=.)(?=\\p{Upper})".toRegex() } | ||
|
||
fun String.toKebabCase() = replace(humps, "-").lowercase(Locale.getDefault()) | ||
|
||
fun VelocityEngine.writeTemplate( | ||
templateName: String, | ||
targetDirectory: File, | ||
targetName: String, | ||
context: VelocityContext | ||
) { | ||
val targetFile = targetDirectory.resolve("${targetName}.kt") | ||
if (targetFile.exists()) { | ||
println("Can't write $templateName to $targetFile, file already exists. Delete and run again.") | ||
return | ||
} | ||
println("--> Writing to $targetFile...") | ||
val template = getTemplate(templateName) | ||
val writer = StringWriter() | ||
template.merge(context, writer) | ||
targetFile.writeText(writer.toString()) | ||
} | ||
|
||
// main code | ||
|
||
if (args.isEmpty()) { | ||
printUsage() | ||
exitProcess(1) | ||
} | ||
val newRule = args.singleOrNull() | ||
when { | ||
newRule == null -> { | ||
println("Only 1 parameter supported.") | ||
printUsage() | ||
exitProcess(2) | ||
} | ||
|
||
newRule.endsWith("Rule") || newRule.endsWith("Check") -> { | ||
println("Do not add 'Rule' or 'Check' suffix, it will result in weird and repetitive naming.") | ||
printUsage() | ||
exitProcess(2) | ||
} | ||
} | ||
|
||
val ruleName = requireNotNull(newRule) | ||
|
||
println("Finding project root...") | ||
var rootDir = File(System.getProperty("user.dir")) | ||
while (!rootDir.resolve("settings.gradle.kts").exists()) { | ||
rootDir = rootDir.parentFile | ||
} | ||
|
||
println("Setting up templates...") | ||
|
||
val engine = VelocityEngine( | ||
Properties().apply { | ||
setProperty( | ||
RuntimeConstants.FILE_RESOURCE_LOADER_PATH, | ||
"templates" | ||
) // Adjust the path to your templates directory | ||
} | ||
).apply { init() } | ||
|
||
val context = VelocityContext() | ||
context.apply { | ||
put("ruleName", ruleName) | ||
put("detektRuleName", "${newRule}Check") | ||
put("ktlintRuleName", "${newRule}Check") | ||
put("ktlintRuleId", ruleName.toKebabCase()) | ||
} | ||
|
||
println("Applying templates...") | ||
|
||
// Write main rule | ||
engine.writeTemplate( | ||
templateName = "Rule.kt.template", | ||
targetDirectory = rootDir.resolve("rules/common/src/main/kotlin/io/nlopez/compose/rules/"), | ||
targetName = ruleName, | ||
context = context | ||
) | ||
|
||
// Write detekt rule that delegates to main rule | ||
engine.writeTemplate( | ||
templateName = "DetektRule.kt.template", | ||
targetDirectory = rootDir.resolve("rules/detekt/src/main/kotlin/io/nlopez/compose/rules/detekt/"), | ||
targetName = "${ruleName}Check", | ||
context = context | ||
) | ||
|
||
// Write test for detekt rule | ||
engine.writeTemplate( | ||
templateName = "DetektRuleTest.kt.template", | ||
targetDirectory = rootDir.resolve("rules/detekt/src/test/kotlin/io/nlopez/compose/rules/detekt/"), | ||
targetName = "${ruleName}CheckTest", | ||
context = context | ||
) | ||
|
||
// Write ktlint rule that delegates to main rule | ||
engine.writeTemplate( | ||
templateName = "KtlintRule.kt.template", | ||
targetDirectory = rootDir.resolve("rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/"), | ||
targetName = "${ruleName}Check", | ||
context = context | ||
) | ||
|
||
// Write test for ktlint rule | ||
engine.writeTemplate( | ||
templateName = "KtlintRuleTest.kt.template", | ||
targetDirectory = rootDir.resolve("rules/ktlint/src/test/kotlin/io/nlopez/compose/rules/ktlint/"), | ||
targetName = "${ruleName}CheckTest", | ||
context = context | ||
) | ||
// Desirable improvements to add: | ||
// - add to detekt's default ruleset yml rules/detekt/src/main/resources/config/config.yml | ||
// - add rule to docs/detekt.md "default rule" values | ||
// - add entry in docs/rules.md (likely at the end) |
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,21 @@ | ||
package io.nlopez.compose.rules.detekt | ||
|
||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Debt | ||
import io.gitlab.arturbosch.detekt.api.Issue | ||
import io.gitlab.arturbosch.detekt.api.Severity | ||
import io.nlopez.compose.core.ComposeKtVisitor | ||
import io.nlopez.compose.rules.${ruleName} | ||
import io.nlopez.compose.rules.DetektRule | ||
|
||
class ${detektRuleName}(config: Config) : | ||
DetektRule(config), | ||
ComposeKtVisitor by ${ruleName}() { | ||
|
||
override val issue: Issue = Issue( | ||
id = "${ruleName}", | ||
severity = Severity.CodeSmell, | ||
description = ${ruleName}.${ruleName}ErrorMessage, | ||
debt = Debt.FIVE_MINS, | ||
) | ||
} |
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,42 @@ | ||
package io.nlopez.compose.rules.detekt | ||
|
||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.SourceLocation | ||
import io.gitlab.arturbosch.detekt.test.assertThat | ||
import io.gitlab.arturbosch.detekt.test.lint | ||
import io.nlopez.compose.rules.${ruleName} | ||
import org.intellij.lang.annotations.Language | ||
import org.junit.jupiter.api.Test | ||
|
||
class ${detektRuleName}Test { | ||
|
||
private val rule = ${detektRuleName}(Config.empty) | ||
|
||
@Test | ||
fun `errors for X case`() { | ||
@Language("kotlin") | ||
val code = | ||
""" | ||
TODO() | ||
""".trimIndent() | ||
val errors = rule.lint(code) | ||
assertThat(errors).hasStartSourceLocations( | ||
SourceLocation(2, 5), | ||
) | ||
for (error in errors) { | ||
assertThat(error) | ||
.hasMessage(${ruleName}.${ruleName}ErrorMessage) | ||
} | ||
} | ||
|
||
@Test | ||
fun `passes for X case`() { | ||
@Language("kotlin") | ||
val code = | ||
""" | ||
TODO() | ||
""".trimIndent() | ||
val errors = rule.lint(code) | ||
assertThat(errors).isEmpty() | ||
} | ||
} |
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,9 @@ | ||
package io.nlopez.compose.rules.ktlint | ||
|
||
import io.nlopez.compose.core.ComposeKtVisitor | ||
import io.nlopez.compose.rules.${ruleName} | ||
import io.nlopez.compose.rules.KtlintRule | ||
|
||
class ${ruleName} : | ||
KtlintRule("compose:${ktlintRuleId}"), | ||
ComposeKtVisitor by ${ruleName}() |
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,38 @@ | ||
package io.nlopez.compose.rules.ktlint | ||
|
||
import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule | ||
import com.pinterest.ktlint.test.LintViolation | ||
import io.nlopez.compose.rules.${ruleName} | ||
import org.intellij.lang.annotations.Language | ||
import org.junit.jupiter.api.Test | ||
|
||
class ${ktlintRuleName}Test { | ||
|
||
private val ruleAssertThat = assertThatRule { ${ktlintRuleName}() } | ||
|
||
@Test | ||
fun `errors for X case`() { | ||
@Language("kotlin") | ||
val code = | ||
""" | ||
TODO() | ||
""".trimIndent() | ||
ruleAssertThat(code).hasLintViolationsWithoutAutoCorrect( | ||
LintViolation( | ||
line = 2, | ||
col = 5, | ||
detail = ${ruleName}.${ruleName}ErrorMessage, | ||
), | ||
) | ||
} | ||
|
||
@Test | ||
fun `passes for X case`() { | ||
@Language("kotlin") | ||
val code = | ||
""" | ||
TODO() | ||
""".trimIndent() | ||
ruleAssertThat(code).hasNoLintViolations() | ||
} | ||
} |
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,14 @@ | ||
package io.nlopez.compose.rules | ||
|
||
import io.nlopez.compose.core.ComposeKtVisitor | ||
|
||
class ${ruleName} : ComposeKtVisitor { | ||
|
||
companion object { | ||
val ${ruleName}ErrorMessage = """ | ||
TODO | ||
|
||
See https://mrmans0n.github.io/compose-rules/rules/#TODO for more information. | ||
""".trimIndent() | ||
} | ||
} |