Android Plataforma - Parte 15: Cuidando do código com Detekt, Klint e Spotless
Rodrigo Sicarelli
Posted on September 27, 2023
🌱 Branch: 15/enhancing-code-quality
🔗 Repositório: github.com/rsicarelli/kotlin-gradle-android-platform
⬅️ Artigo Anterior: Parte 14: Aderindo a funcionalidades experimentais do compilador do Kotlin
➡️ Próximo Artigo: Parte 16: Considerações finais
No último artigo, abordamos a capacidade de nossa plataforma aderir a funcionalidades experimentais em diferentes módulos.
Agora, vamos explorar a garantia da qualidade do código através da integração de plugins.
Por que focar em automatizar verificações do código?
Quando se trabalha em equipe, é vital ter padrões de estilo e nomenclatura para manter a consistência. Estabelecer um padrão sólido ajuda a reduzir a sobrecarga de decisões e facilita a colaboração.
Pense assim: ao se juntar a uma orquestra, seguimos a pessoa contudora que dita ritmo da música. É o mesmo com nosso módulos; seguimos padrões preestabelecidos e acordados pelo time de forma automatizada.
Essa prática é especialmente útil para quando uma pessoa nova entra no time, além de que os acordos fiquem documentados e codificados, abertos para colaboração.
Incluíndo análise de código estático com Detekt
O Detekt
é talvez a ferramenta mais famosa em Kotlin para analisar o código e garantir que algumas práticas são aplicadas.
Não iremos focar muito nas suas funcionalidades, vamos direto pra implementação
Passo a passo
1 - Vamos começar declarando o detekt
no nossos libs.versions.toml
:
[versions]
detekt = "1.23.1"
detektCompose = "0.2.3"
[libraries]
gradlePlugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
detektRules-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" }
detektRules-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
detektRules-libraries = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" }
[plugins]
arturbosch-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
2 - Sincronize o projeto. Navegue até build-logic/build.gradle.kts
e vamos compilar a dependencia do detekt
na nossa plataforma:
dependencies {
compileOnly(libs.gradlePlugin.android)
compileOnly(libs.gradlePlugin.kotlin)
compileOnly(libs.gradlePlugin.detekt)
}
3 - Sincronize o projeto. Agora, vamos declarar nossa DSL do DetektOptions
.
Crie um arquivo DetektOptions
em build-logic/src/../options
data class DetektOptions(
val parallel: Boolean,
val buildUponDefaultConfig: Boolean,
val configFileNames: List<String>,
val includes: List<String>,
val excludes: List<String>
)
class DetektOptionsBuilder {
var parallel: Boolean = true
var configFiles: List<String> = listOf(".detekt.yml, .detekt-compose.yml")
var buildUponDefaultConfig: Boolean = true
var includes: List<String> = listOf("**/*.kt", "**/*.kts")
var excludes: List<String> = listOf(".*/resources/.*", ".*/build/.*")
internal fun build(): DetektOptions = DetektOptions(
parallel = parallel,
configFileNames = configFiles,
includes = includes,
excludes = excludes,
buildUponDefaultConfig = buildUponDefaultConfig
)
}
4 - Em seguida, crie um novo arquivo detekt.kt
em build-logic/src/.../decorations
e declare uma função applyDetekt()
Essa configurações impoe que:
- Esse plugin só poderá ser chamado no
build.gradle.kts
da raíz - Exista um arquivo
.detekt.yml
na raíz do projeto - Exista um arquivo
.detekt-compose.yml
na raíz do projeto
import com.rsicarelli.kplatform.options.DetektOptions
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.withType
internal fun Project.applyDetekt(
detektOptions: DetektOptions
) {
check(rootProject == this) { "Must be called on a root project" }
pluginManager.apply("io.gitlab.arturbosch.detekt")
extensions.configure<DetektExtension> {
parallel = detektOptions.parallel
toolVersion = libs.version("detekt")
buildUponDefaultConfig = detektOptions.buildUponDefaultConfig
config.setFrom(detektOptions.configFileNames.map { "$rootDir/$it" })
}
tasks.withType<Detekt> {
setSource(files(projectDir))
include(detektOptions.includes)
exclude(detektOptions.excludes)
}
addDetektPlugins(listOf("compose", "formatting", "libraries"))
}
fun Project.addDetektPlugins(detektPlugins: List<String>) {
fun DependencyHandlerScope.detektPlugin(dependency: MinimalExternalModuleDependency) {
add("detektPlugins", dependency)
}
dependencies {
detektPlugins.forEach { plugin ->
detektPlugin(libs.findLibrary("detektRules-$plugin").get().get())
}
}
}
5 - Em seguida. vamos expor essa decoração no KPlatformPlugin.kt
:
fun Project.detekt(builderAction: DetektBuilder = {}) =
applyDetekt(DetektOptionsBuilder().apply(builderAction).build())
6 - Sincronize o projeto. Em seguida, va até o build.gradle.kts
da raiz do projeto, e inclua o plugin do detekt
:
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.arturbosch.detekt) apply false
id(libs.plugins.rsicarelli.kplatform.get().pluginId)
}
6 - Sincronize o projeto. Em seguida, aplique a decoração detekt()
no mesmo arquivo:
import com.rsicarelli.kplatform.detekt
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.arturbosch.detekt) apply false
id(libs.plugins.rsicarelli.kplatform.get().pluginId)
}
detekt()
7 - Vamos criar 2 arquivos na raíz do projeto: .detekt.yml
e .detekt-compose.yml
8 - Sincronize o projeto. Perceba que uma série de tasks detektX
foram adicionadas no projeto:
8 - Verifique que está funcionando rodando o seguinte comando.
Alternativamente, pode simplesmente dar um clique duplo na task detekt
na lista de tasks do Gradle:
./gradlew detekt
Você vai perceber que vamos ter várias penalidades.
A seguir, vamos usar o Spotless para nos ajudar a reduzir a lista de faltas.
Adicionando o Spotless
O spotless é outra ferramenta indispensável nos projetos Kotlin.
Seu objetivo é formatar seu código magicamente de acordo com um estilo de código/configurações pre estabelecidas.
Novamente, não vamos focar muito nos detalhes da biblioteca, vamos direto ao uso
Passo a passo
1 - Declare as coordenadas do spotless
no libs.versions.toml
[versions]
spotless = "6.21.0"
[libraries]
gradlePlugin-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
[plugins]
diffplug-spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
2 - Sincronize o projeto. Em seguida, vamos criar os arquivos SpotlessOptions
na pasta build-logic/src/.../options
:
Aqui, nossa plataforma será capaz de:
- Fornecer 2 configurações padrões para o projeto:
SpotlessKtsRule
eSpotlessXmlRule
. Isso configura o spotless para nossos arquivos Gradle com extensão.kts
, além de.xml
do Android. - Possibilta outras configurações de arquivos, de acordo com a necessidade de cada projeto.
data class SpotlessOptions(
val fileRules: List<SpotlessFileRule> = listOf(SpotlessKtRule, SpotlessXmlRule),
)
interface SpotlessFileRule {
val fileExtension: String
val targets: List<String>
val excludes: List<String>
}
object SpotlessKtsRule : SpotlessFileRule {
override val fileExtension: String = "kts"
override val targets: List<String> = listOf("**/*.kts")
override val excludes: List<String> = listOf("**/build/**/*.kts")
}
object SpotlessXmlRule : SpotlessFileRule {
override val fileExtension: String = "xml"
override val targets: List<String> = listOf("**/*.xml")
override val excludes: List<String> = listOf("**/build/**/*.xml")
}
class SpotlessOptionsBuilder {
var fileRules: List<SpotlessFileRule> = listOf(SpotlessKtRule, SpotlessXmlRule)
internal fun build(): SpotlessOptions = SpotlessOptions(
fileRules = fileRules
)
}
3 - Vamos criar um arquivo spotless.kt
dentro de build-logic/src/.../decorations
e declarar a função applySpotless()
Note que:
- Estamos aplicando o Spotless no projeto raíz. Isso faz com que as formatações também aconteçam nos scripts da raíz, assim como na plataforma
build-logic
- Estamos aplicando o Spotless também para todos os sub projetos.
- Estamos utilizando o
klint
como regras doSpotless
- O plugin assume que existe um arquivo
.editorconfig
na raíz do projeto
import com.diffplug.gradle.spotless.SpotlessExtension
import com.diffplug.gradle.spotless.SpotlessPlugin
import com.rsicarelli.kplatform.options.SpotlessOptions
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
internal fun Project.applySpotless(spotlessConfig: SpotlessOptions) {
val project = this
configureSpotlessPlugin(spotlessConfig, project)
rootProject.subprojects {
configureSpotlessPlugin(spotlessConfig, project)
}
}
private fun Project.configureSpotlessPlugin(
spotlessConfig: SpotlessOptions,
project: Project
) {
apply<SpotlessPlugin>()
extensions.configure<SpotlessExtension> {
kotlin {
target("src/**/*.kt")
ktlint().setEditorConfigPath("${project.rootDir}/.editorconfig")
}
spotlessConfig.fileRules.forEach { spotlessFileRule ->
with(spotlessFileRule) {
format(fileExtension) {
target(targets)
targetExclude(excludes)
}
}
}
}
}
4 - Crie um arquivo .editorconfig
na raiz do projeto:
5 - Vamos expor essa decoração no KPlatformPlugin.kt
:
fun Project.spotless(builderAction: SpotlessBuilder = { }) =
applySpotless(SpotlessOptionsBuilder().apply(builderAction).build())
6 - Sincronize o projeto. Em seguida, navegue até build.gradle.kts
da raiz do projeto e declare o plugin do spotless:
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.arturbosch.detekt) apply false
alias(libs.plugins.diffplug.spotless) apply false
id(libs.plugins.rsicarelli.kplatform.get().pluginId)
}
7 - Sincronize o projeto. Em seguida. altere o mesmo build.gradle.kts
e aplique a decoração spotless()
:
import com.rsicarelli.kplatform.detekt
import com.rsicarelli.kplatform.spotless
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.arturbosch.detekt) apply false
alias(libs.plugins.diffplug.spotless) apply false
id(libs.plugins.rsicarelli.kplatform.get().pluginId)
}
detekt()
spotless()
8 - Sincronize o projeto. Perceba que agora várias tasks spotless
estarão disponíveis na lista de tarefas do Gradle:
9 - Verifque o funcionamento rodando o comando, ou clique duplo na tarefa spotlessApply
na lista de tarefas do Gradle:
./gradlew spotlessApply
Sucesso!
O Spotless vai conseguir solucionar várias faltas automaticamente pra gente. Porém, tem algumas, como por exemplo nomeação dos arquivos, que não é suportado pelo Spotless.
Aproveitando, nessa branch, adicionei várias documentações para todas nossas API's da plataforma!
No próximo artigo, iremos fechar o essa série de posts, e contar um pouquinho sobre os próximos passos!
Posted on September 27, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 27, 2023