Skip to content

Commit 0db2dad

Browse files
committed
feat: lint setup
1 parent b360586 commit 0db2dad

File tree

6 files changed

+604
-18
lines changed

6 files changed

+604
-18
lines changed

.editorconfig

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
root = true
22

3-
# Apply to all files
43
[*]
4+
charset = utf-8
55
end_of_line = lf
6+
indent_size = 4
7+
indent_style = space
68
insert_final_newline = true
9+
max_line_length = 120
710
trim_trailing_whitespace = true
8-
max_line_length=120
9-
indent_style = space
10-
indent_size = 4
11+
12+
[*.{kt,kts}]
13+
ij_kotlin_allow_trailing_comma = true
14+
ij_kotlin_allow_trailing_comma_on_call_site = true
15+
ktlint_code_style = android_studio
16+
ktlint_experimental = enabled
17+
ktlint_function_naming_ignore_when_annotated_with = Composable
18+
ktlint_standard = enabled
1119

1220
[*.md]
13-
max_line_length = off
1421
indent_size = 2
22+
max_line_length = off
1523
trim_trailing_whitespace = false
1624

1725
[*.js]
1826
indent_size = 2
1927

2028
[*.json]
21-
max_line_length = off
2229
indent_size = 2
23-
24-
[*.yml]
2530
max_line_length = off
31+
32+
[*.{yml,yaml}]
2633
indent_size = 2
34+
max_line_length = off

.github/workflows/lint.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [ "master" ]
6+
pull_request:
7+
branches: [ "master" ]
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
env:
14+
# Force colored output in CI
15+
TERM: xterm-256color
16+
FORCE_COLOR: 1
17+
CI: true
18+
19+
jobs:
20+
lint:
21+
name: Lint Checks
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 15
24+
25+
permissions:
26+
# Required for all workflows
27+
contents: read
28+
actions: read
29+
# Required for SARIF upload to GitHub Security tab
30+
security-events: write
31+
32+
steps:
33+
- name: 📥 Checkout code
34+
uses: actions/checkout@v4
35+
with:
36+
37+
- name: ☕ Setup JDK 17
38+
uses: actions/setup-java@v4
39+
with:
40+
java-version: '17'
41+
distribution: 'adopt'
42+
43+
- name: 🐘 Setup Gradle
44+
uses: gradle/actions/setup-gradle@v4
45+
with:
46+
cache-read-only: true
47+
48+
- name: 🎨 Run Spotless Check
49+
run: |
50+
./gradlew spotlessCheck --no-daemon --stacktrace
51+
52+
- name: 🔍 Run Detekt Analysis
53+
run: |
54+
./gradlew detekt --no-daemon --stacktrace
55+
continue-on-error: true
56+
57+
- name: 🔒 Upload Detekt SARIF to GitHub Security
58+
if: always()
59+
uses: github/codeql-action/upload-sarif@v3
60+
with:
61+
sarif_file: app/build/reports/detekt/detekt.sarif
62+
category: detekt
63+
continue-on-error: true
64+
65+
- name: 📎 Upload Artifacts
66+
if: always()
67+
uses: actions/upload-artifact@v4
68+
with:
69+
name: lint-reports-${{ github.run_number }}
70+
path: |
71+
app/build/reports/detekt/
72+
retention-days: 30

LINTING.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 🔍 Linting Setup
2+
3+
This project uses a linting setup with default configurations for Android development with Kotlin and Jetpack Compose.
4+
5+
## 🛠️ Tools
6+
7+
- **Detekt** - Static code analysis with default configuration + Compose rules plugin
8+
- **Spotless** - Code formatting orchestrator + ktlint with default rules
9+
- **GitHub Actions** - via `.github/workflows/lint.yml`
10+
- **GitHub Advanced Security** - Code scanning with SARIF reports
11+
12+
Recommended Android Studio plugins:
13+
- **Detekt** - code analysis
14+
- **Ktlint** - formatting feedback and quick fixes
15+
16+
## Commands
17+
18+
```sh
19+
./gradlew preCommit # pre-commit check
20+
./gradlew formatAll # auto-fix formatting
21+
22+
# Detekt
23+
./gradlew detekt
24+
./gradlew detektBaseline # update baseline with current violations
25+
26+
# Spotless
27+
./gradlew spotlessCheck
28+
./gradlew spotlessApply
29+
```
30+
31+
## 📊 Reports
32+
33+
Reports are generated in:
34+
- **Detekt**: `app/build/reports/detekt/`

app/build.gradle.kts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ plugins {
1212
alias(libs.plugins.google.services)
1313
alias(libs.plugins.protobuf)
1414
alias(libs.plugins.room)
15+
alias(libs.plugins.detekt)
16+
alias(libs.plugins.spotless)
1517
}
1618

1719
// https://developer.android.com/studio/publish/app-signing#secure-key
@@ -83,8 +85,11 @@ android {
8385
keyPassword = "android"
8486
}
8587
create("release") {
86-
val keystoreFile = keystoreProperties.getProperty("storeFile").takeIf { it.isNotBlank() }
87-
?.let { rootProject.file(it) }
88+
val keystoreFile =
89+
keystoreProperties
90+
.getProperty("storeFile")
91+
.takeIf { it.isNotBlank() }
92+
?.let { rootProject.file(it) }
8893
storeFile = if (keystoreFile?.exists() == true) keystoreFile else null
8994
// storeFile = rootProject.file(keystoreProperties.getProperty("storeFile"))
9095
storePassword = keystoreProperties.getProperty("storePassword")
@@ -105,7 +110,7 @@ android {
105110
isShrinkResources = false
106111
proguardFiles(
107112
getDefaultProguardFile("proguard-android-optimize.txt"),
108-
"proguard-rules.pro"
113+
"proguard-rules.pro",
109114
)
110115
signingConfig = signingConfigs.getByName("release")
111116
ndk {
@@ -137,7 +142,7 @@ android {
137142
}
138143
testOptions {
139144
unitTests {
140-
isReturnDefaultValues = true // mockito
145+
isReturnDefaultValues = true // mockito
141146
isIncludeAndroidResources = true // robolectric
142147
}
143148
}
@@ -171,12 +176,46 @@ protobuf {
171176
}
172177

173178
composeCompiler {
174-
featureFlags = setOf(
175-
ComposeFeatureFlag.StrongSkipping.disabled(),
176-
ComposeFeatureFlag.OptimizeNonSkippingGroups,
177-
)
179+
featureFlags =
180+
setOf(
181+
ComposeFeatureFlag.StrongSkipping.disabled(),
182+
ComposeFeatureFlag.OptimizeNonSkippingGroups,
183+
)
178184
reportsDestination = layout.buildDirectory.dir("compose_compiler")
179185
}
186+
187+
// Linting Configuration
188+
val isCI = System.getenv("CI").toBoolean()
189+
190+
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
191+
ignoreFailures = !isCI
192+
reports {
193+
html.required.set(!isCI)
194+
md.required.set(!isCI)
195+
sarif.required.set(true)
196+
txt.required.set(false)
197+
xml.required.set(isCI)
198+
}
199+
}
200+
201+
spotless {
202+
val ktlintVersion = "1.0.1"
203+
kotlin {
204+
target("**/*.kt")
205+
targetExclude("**/build/**/*.kt", "**/generated/**/*.kt")
206+
ktlint(ktlintVersion).setEditorConfigPath(rootProject.file(".editorconfig").absolutePath)
207+
trimTrailingWhitespace()
208+
endWithNewline()
209+
}
210+
211+
kotlinGradle {
212+
target("*.gradle.kts")
213+
ktlint(ktlintVersion)
214+
trimTrailingWhitespace()
215+
endWithNewline()
216+
}
217+
}
218+
180219
dependencies {
181220
implementation(fileTree("libs") { include("*.aar") })
182221
implementation(libs.jna) { artifact { type = "aar" } }
@@ -273,10 +312,14 @@ dependencies {
273312
testImplementation(libs.test.mockito.kotlin)
274313
testImplementation(libs.test.robolectric)
275314
testImplementation(libs.test.turbine)
315+
// Linting
316+
detektPlugins(libs.detekt.formatting)
317+
detektPlugins(libs.detekt.compose.rules)
276318
}
277319
ksp {
278-
// cool but strict: https://developer.android.com/jetpack/androidx/releases/room#2.6.0
279-
// arg("room.generateKotlin", "true")
320+
arg("room.schemaLocation", "$projectDir/schemas")
321+
arg("room.incremental", "true")
322+
arg("room.expandProjection", "true")
280323
}
281324
// https://developer.android.com/jetpack/androidx/releases/room#gradle-plugin
282325
room {
@@ -288,3 +331,24 @@ tasks.withType<Test> {
288331
// showStandardStreams = true
289332
}
290333
}
334+
335+
tasks.register("lintAll") {
336+
group = "verification"
337+
description = "Run all linting tools"
338+
dependsOn("detekt", "spotlessCheck")
339+
}
340+
341+
tasks.register("formatAll") {
342+
group = "formatting"
343+
description = "Apply all formatting tools"
344+
dependsOn("spotlessApply")
345+
}
346+
347+
tasks.register("preCommit") {
348+
group = "verification"
349+
description = "Run checks that should pass before committing (fast subset)"
350+
dependsOn("spotlessCheck")
351+
doLast {
352+
println("✅ Pre-commit checks passed!")
353+
}
354+
}

0 commit comments

Comments
 (0)