android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: detekt
|
|
3
|
+
description: >
|
|
4
|
+
Detekt static analysis for Kotlin Android projects.
|
|
5
|
+
Load this skill when setting up Detekt, configuring rules,
|
|
6
|
+
writing custom Detekt rules, suppressing false positives,
|
|
7
|
+
integrating Detekt into CI, or generating reports.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Detekt
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Detekt is a static code analysis tool for Kotlin. It detects code smells, complexity issues, style violations, and potential bugs. Unlike Android Lint (which targets Android-specific patterns), Detekt focuses on general Kotlin code quality. Both should be used together.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Detekt runs on **Kotlin source** — not bytecode; catches issues before compilation
|
|
20
|
+
- Configure via **`detekt.yml`** — enable/disable rules, set thresholds
|
|
21
|
+
- **Baseline** for existing codebases — suppress existing violations, only check new code
|
|
22
|
+
- Run in **CI** — fail the build on new violations
|
|
23
|
+
- **Type resolution** mode gives more accurate results but is slower
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
// root build.gradle.kts
|
|
31
|
+
plugins {
|
|
32
|
+
alias(libs.plugins.detekt) apply false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// app/build.gradle.kts (or in convention plugin)
|
|
36
|
+
plugins {
|
|
37
|
+
alias(libs.plugins.detekt)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
detekt {
|
|
41
|
+
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
|
|
42
|
+
baseline = file("$rootDir/config/detekt/baseline.xml")
|
|
43
|
+
buildUponDefaultConfig = true // start from default, override in yml
|
|
44
|
+
allRules = false // don't enable all rules by default
|
|
45
|
+
autoCorrect = false // set true to auto-fix style issues
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dependencies {
|
|
49
|
+
detektPlugins(libs.detekt.formatting) // ktlint-based formatting rules
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```toml
|
|
54
|
+
# libs.versions.toml
|
|
55
|
+
[versions]
|
|
56
|
+
detekt = "1.23.6"
|
|
57
|
+
|
|
58
|
+
[libraries]
|
|
59
|
+
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
|
60
|
+
|
|
61
|
+
[plugins]
|
|
62
|
+
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## detekt.yml Configuration
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
# config/detekt/detekt.yml
|
|
71
|
+
|
|
72
|
+
build:
|
|
73
|
+
maxIssues: 0 # fail on any new issue
|
|
74
|
+
|
|
75
|
+
complexity:
|
|
76
|
+
LongMethod:
|
|
77
|
+
threshold: 40 # max lines per function
|
|
78
|
+
LongParameterList:
|
|
79
|
+
functionThreshold: 6
|
|
80
|
+
constructorThreshold: 7
|
|
81
|
+
TooManyFunctions:
|
|
82
|
+
thresholdInFiles: 20
|
|
83
|
+
thresholdInClasses: 15
|
|
84
|
+
CyclomaticComplexMethod:
|
|
85
|
+
threshold: 15
|
|
86
|
+
|
|
87
|
+
naming:
|
|
88
|
+
FunctionNaming:
|
|
89
|
+
functionPattern: '[a-z][a-zA-Z0-9]*'
|
|
90
|
+
excludes: ['**/test/**', '**/androidTest/**']
|
|
91
|
+
ClassNaming:
|
|
92
|
+
classPattern: '[A-Z][a-zA-Z0-9]*'
|
|
93
|
+
VariableNaming:
|
|
94
|
+
variablePattern: '[a-z][A-Za-z0-9]*'
|
|
95
|
+
|
|
96
|
+
style:
|
|
97
|
+
MagicNumber:
|
|
98
|
+
active: true
|
|
99
|
+
ignoreNumbers: ['-1', '0', '1', '2']
|
|
100
|
+
ignoreHashCodeFunction: true
|
|
101
|
+
ignorePropertyDeclaration: true
|
|
102
|
+
WildcardImport:
|
|
103
|
+
active: true
|
|
104
|
+
UnusedPrivateMember:
|
|
105
|
+
active: true
|
|
106
|
+
ForbiddenComment:
|
|
107
|
+
active: true
|
|
108
|
+
values: ['FIXME:', 'STOPSHIP:']
|
|
109
|
+
|
|
110
|
+
exceptions:
|
|
111
|
+
TooGenericExceptionCaught:
|
|
112
|
+
active: true
|
|
113
|
+
exceptionNames: ['Exception', 'Throwable']
|
|
114
|
+
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
|
115
|
+
SwallowedException:
|
|
116
|
+
active: true
|
|
117
|
+
|
|
118
|
+
coroutines:
|
|
119
|
+
GlobalCoroutineUsage:
|
|
120
|
+
active: true
|
|
121
|
+
RedundantSuspendModifier:
|
|
122
|
+
active: true
|
|
123
|
+
SuspendFunWithFlowReturnType:
|
|
124
|
+
active: true
|
|
125
|
+
|
|
126
|
+
formatting:
|
|
127
|
+
MaximumLineLength:
|
|
128
|
+
maxLineLength: 120
|
|
129
|
+
NoUnusedImports:
|
|
130
|
+
active: true
|
|
131
|
+
TrailingCommaOnCallSite:
|
|
132
|
+
active: false
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Running Detekt
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# ✅ Run all checks
|
|
141
|
+
./gradlew detekt
|
|
142
|
+
|
|
143
|
+
# ✅ Run with type resolution (more accurate, slower)
|
|
144
|
+
./gradlew detektMain
|
|
145
|
+
|
|
146
|
+
# ✅ Auto-fix formatting issues
|
|
147
|
+
./gradlew detekt --auto-correct
|
|
148
|
+
|
|
149
|
+
# ✅ Generate HTML report
|
|
150
|
+
./gradlew detekt
|
|
151
|
+
# Report at: app/build/reports/detekt/detekt.html
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Baseline (for existing projects)
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# ✅ Generate baseline from current violations
|
|
160
|
+
./gradlew detektBaseline
|
|
161
|
+
|
|
162
|
+
# This creates baseline.xml with all current issues
|
|
163
|
+
# Future runs only fail on NEW violations
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Suppressing Violations
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
// ✅ Suppress a specific rule inline
|
|
172
|
+
@Suppress("MagicNumber")
|
|
173
|
+
val timeout = 30_000L
|
|
174
|
+
|
|
175
|
+
// ✅ Suppress multiple rules
|
|
176
|
+
@Suppress("LongMethod", "ComplexMethod")
|
|
177
|
+
fun complexLegacyFunction() { ... }
|
|
178
|
+
|
|
179
|
+
// ✅ Suppress in detekt.yml for entire paths
|
|
180
|
+
style:
|
|
181
|
+
MagicNumber:
|
|
182
|
+
excludes: ['**/test/**', '**/androidTest/**', '**/Preview*.kt']
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Custom Detekt Rule
|
|
188
|
+
|
|
189
|
+
```kotlin
|
|
190
|
+
// ✅ Custom rule — detect hardcoded strings in Compose
|
|
191
|
+
class HardcodedStringInComposeRule(config: Config) : Rule(config) {
|
|
192
|
+
|
|
193
|
+
override val issue = Issue(
|
|
194
|
+
id = "HardcodedStringInCompose",
|
|
195
|
+
severity = Severity.Warning,
|
|
196
|
+
description = "Hardcoded strings in Compose should use string resources",
|
|
197
|
+
debt = Debt.FIVE_MINS
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
override fun visitCallExpression(expression: KtCallExpression) {
|
|
201
|
+
super.visitCallExpression(expression)
|
|
202
|
+
|
|
203
|
+
// Check if it's a Text() composable with a hardcoded string
|
|
204
|
+
if (expression.calleeExpression?.text == "Text") {
|
|
205
|
+
val firstArg = expression.valueArguments.firstOrNull()
|
|
206
|
+
val argText = firstArg?.getArgumentExpression()?.text
|
|
207
|
+
if (argText != null && argText.startsWith("\"")) {
|
|
208
|
+
report(CodeSmell(
|
|
209
|
+
issue = issue,
|
|
210
|
+
entity = Entity.from(expression),
|
|
211
|
+
message = "Use stringResource() instead of hardcoded string: $argText"
|
|
212
|
+
))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Register in custom rule set provider
|
|
219
|
+
class CustomRuleSetProvider : RuleSetProvider {
|
|
220
|
+
override val ruleSetId: String = "custom-rules"
|
|
221
|
+
override fun instance(config: Config) = RuleSet(
|
|
222
|
+
ruleSetId,
|
|
223
|
+
listOf(HardcodedStringInComposeRule(config))
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## CI Integration
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
# .github/workflows/quality.yml
|
|
234
|
+
- name: Run Detekt
|
|
235
|
+
run: ./gradlew detekt
|
|
236
|
+
|
|
237
|
+
- name: Upload Detekt report
|
|
238
|
+
if: failure()
|
|
239
|
+
uses: actions/upload-artifact@v3
|
|
240
|
+
with:
|
|
241
|
+
name: detekt-report
|
|
242
|
+
path: '**/build/reports/detekt/'
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Detekt vs Lint
|
|
248
|
+
|
|
249
|
+
| | Detekt | Android Lint |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| Target | Kotlin code quality | Android-specific issues |
|
|
252
|
+
| Runs on | JVM, CI, pre-commit | Build, CI |
|
|
253
|
+
| Custom rules | Kotlin DSL | PSI/UAST |
|
|
254
|
+
| Auto-fix | ✅ (formatting) | Limited |
|
|
255
|
+
| Best for | Code smells, complexity | Android anti-patterns |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Anti-Patterns
|
|
260
|
+
|
|
261
|
+
- Ignoring all violations with a baseline — baseline is for migration, not ongoing suppression
|
|
262
|
+
- `allRules = true` — too noisy; causes over-suppression
|
|
263
|
+
- Not running Detekt in CI — violations accumulate silently
|
|
264
|
+
- Per-file `@Suppress` without a comment explaining why — future maintainers won't understand
|
|
265
|
+
- Skipping `detekt-formatting` plugin — misses import order and whitespace issues
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Related Skills
|
|
270
|
+
- `lint-rule` — Android Lint for Android-specific checks
|
|
271
|
+
- `gradle` — build integration
|
|
272
|
+
- `convention-plugin` — sharing Detekt config across modules
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lint-rule
|
|
3
|
+
description: >
|
|
4
|
+
Custom Android Lint rules for enforcing project conventions.
|
|
5
|
+
Load this skill when writing custom Lint checks, detecting forbidden
|
|
6
|
+
patterns in code, enforcing naming conventions automatically,
|
|
7
|
+
or integrating custom rules into the build pipeline.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Lint Rule
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Android Lint is a static analysis tool that inspects source code for bugs, style violations, and custom project conventions. You can write custom Lint rules to enforce patterns specific to your project — preventing anti-patterns before they reach code review.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Custom rules run at **build time** — catch issues before they reach review
|
|
20
|
+
- Rules target **PSI (Program Structure Interface)** — the AST of Kotlin/Java files
|
|
21
|
+
- **UAST (Universal AST)** works for both Kotlin and Java — prefer it over PSI
|
|
22
|
+
- Each rule has a unique **Issue ID** — used in `@SuppressLint` and baseline files
|
|
23
|
+
- Rules are packaged in a separate **lint module** — consumed by the app module
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Module Setup
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
project/
|
|
31
|
+
├── app/
|
|
32
|
+
├── lint/ # custom lint rules module
|
|
33
|
+
│ ├── build.gradle.kts
|
|
34
|
+
│ └── src/main/kotlin/
|
|
35
|
+
│ └── com/example/lint/
|
|
36
|
+
│ ├── MyDetector.kt
|
|
37
|
+
│ └── MyIssueRegistry.kt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```kotlin
|
|
41
|
+
// lint/build.gradle.kts
|
|
42
|
+
plugins {
|
|
43
|
+
alias(libs.plugins.kotlin.jvm)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
dependencies {
|
|
47
|
+
compileOnly(libs.lint.api)
|
|
48
|
+
compileOnly(libs.lint.checks)
|
|
49
|
+
testImplementation(libs.lint.tests)
|
|
50
|
+
testImplementation(libs.junit)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```kotlin
|
|
55
|
+
// app/build.gradle.kts
|
|
56
|
+
dependencies {
|
|
57
|
+
lintChecks(project(":lint"))
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Basic Detector Structure
|
|
64
|
+
|
|
65
|
+
```kotlin
|
|
66
|
+
// ✅ Detector that finds a forbidden pattern
|
|
67
|
+
class ForbiddenCallDetector : Detector(), Detector.UastScanners {
|
|
68
|
+
|
|
69
|
+
override fun getApplicableMethodNames(): List<String> = listOf("printStackTrace")
|
|
70
|
+
|
|
71
|
+
override fun visitMethodCall(
|
|
72
|
+
context: JavaContext,
|
|
73
|
+
node: UCallExpression,
|
|
74
|
+
method: PsiMethod
|
|
75
|
+
) {
|
|
76
|
+
context.report(
|
|
77
|
+
issue = ISSUE,
|
|
78
|
+
scope = node,
|
|
79
|
+
location = context.getLocation(node),
|
|
80
|
+
message = "Use structured logging instead of `printStackTrace()`"
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
companion object {
|
|
85
|
+
val ISSUE = Issue.create(
|
|
86
|
+
id = "ForbiddenPrintStackTrace",
|
|
87
|
+
briefDescription = "Use structured logging",
|
|
88
|
+
explanation = """
|
|
89
|
+
`printStackTrace()` logs to stdout which is not captured by crash reporters.
|
|
90
|
+
Use the project's Logger instead.
|
|
91
|
+
""".trimIndent(),
|
|
92
|
+
category = Category.CORRECTNESS,
|
|
93
|
+
priority = 7,
|
|
94
|
+
severity = Severity.ERROR,
|
|
95
|
+
implementation = Implementation(
|
|
96
|
+
ForbiddenCallDetector::class.java,
|
|
97
|
+
Scope.JAVA_FILE_SCOPE
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Issue Registry
|
|
107
|
+
|
|
108
|
+
```kotlin
|
|
109
|
+
// ✅ Register all custom issues
|
|
110
|
+
class MyIssueRegistry : IssueRegistry() {
|
|
111
|
+
override val issues: List<Issue> = listOf(
|
|
112
|
+
ForbiddenCallDetector.ISSUE,
|
|
113
|
+
DirectColorUsageDetector.ISSUE,
|
|
114
|
+
MissingTestTagDetector.ISSUE
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
override val api: Int = CURRENT_API
|
|
118
|
+
override val minApi: Int = 8
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
# lint/src/main/resources/META-INF/services/
|
|
124
|
+
# File: com.android.tools.lint.client.api.IssueRegistry
|
|
125
|
+
com.example.lint.MyIssueRegistry
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Practical Examples
|
|
131
|
+
|
|
132
|
+
```kotlin
|
|
133
|
+
// ✅ Detect direct color usage (enforce design tokens)
|
|
134
|
+
class DirectColorUsageDetector : ResourceXmlDetector() {
|
|
135
|
+
|
|
136
|
+
override fun getApplicableAttributes(): Collection<String> =
|
|
137
|
+
listOf("textColor", "background", "backgroundTint", "tint")
|
|
138
|
+
|
|
139
|
+
override fun visitAttribute(context: XmlContext, attribute: Attr) {
|
|
140
|
+
val value = attribute.value
|
|
141
|
+
if (value.startsWith("#") || value.startsWith("@android:color/")) {
|
|
142
|
+
context.report(
|
|
143
|
+
issue = ISSUE,
|
|
144
|
+
location = context.getLocation(attribute),
|
|
145
|
+
message = "Use design token colors instead of direct color values (`$value`)"
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
companion object {
|
|
151
|
+
val ISSUE = Issue.create(
|
|
152
|
+
id = "DirectColorUsage",
|
|
153
|
+
briefDescription = "Use design token colors",
|
|
154
|
+
explanation = "Direct color values bypass the design system. Use theme attributes.",
|
|
155
|
+
category = Category.CORRECTNESS,
|
|
156
|
+
priority = 6,
|
|
157
|
+
severity = Severity.WARNING,
|
|
158
|
+
implementation = Implementation(
|
|
159
|
+
DirectColorUsageDetector::class.java,
|
|
160
|
+
Scope.RESOURCE_FILE_SCOPE
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ✅ Detect GlobalScope usage
|
|
167
|
+
class GlobalScopeDetector : Detector(), Detector.UastScanners {
|
|
168
|
+
|
|
169
|
+
override fun getApplicableReferenceNames(): List<String> = listOf("GlobalScope")
|
|
170
|
+
|
|
171
|
+
override fun visitReference(
|
|
172
|
+
context: JavaContext,
|
|
173
|
+
reference: UReferenceExpression,
|
|
174
|
+
referenced: PsiElement
|
|
175
|
+
) {
|
|
176
|
+
context.report(
|
|
177
|
+
issue = ISSUE,
|
|
178
|
+
scope = reference,
|
|
179
|
+
location = context.getLocation(reference),
|
|
180
|
+
message = "Avoid `GlobalScope` — use `viewModelScope`, `lifecycleScope`, or an injected scope instead"
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
companion object {
|
|
185
|
+
val ISSUE = Issue.create(
|
|
186
|
+
id = "GlobalScopeUsage",
|
|
187
|
+
briefDescription = "Avoid GlobalScope",
|
|
188
|
+
explanation = "GlobalScope creates unstructured coroutines that leak and can't be cancelled.",
|
|
189
|
+
category = Category.CORRECTNESS,
|
|
190
|
+
priority = 8,
|
|
191
|
+
severity = Severity.ERROR,
|
|
192
|
+
implementation = Implementation(
|
|
193
|
+
GlobalScopeDetector::class.java,
|
|
194
|
+
Scope.JAVA_FILE_SCOPE
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Testing Lint Rules
|
|
204
|
+
|
|
205
|
+
```kotlin
|
|
206
|
+
// ✅ Test a custom lint rule
|
|
207
|
+
class ForbiddenCallDetectorTest {
|
|
208
|
+
|
|
209
|
+
@Test
|
|
210
|
+
fun `detects printStackTrace call`() {
|
|
211
|
+
lint()
|
|
212
|
+
.files(
|
|
213
|
+
kotlin("""
|
|
214
|
+
fun example() {
|
|
215
|
+
Exception("test").printStackTrace()
|
|
216
|
+
}
|
|
217
|
+
""").indented()
|
|
218
|
+
)
|
|
219
|
+
.issues(ForbiddenCallDetector.ISSUE)
|
|
220
|
+
.run()
|
|
221
|
+
.expect("""
|
|
222
|
+
src/test.kt:2: Error: Use structured logging instead of `printStackTrace()` [ForbiddenPrintStackTrace]
|
|
223
|
+
Exception("test").printStackTrace()
|
|
224
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
225
|
+
1 errors, 0 warnings
|
|
226
|
+
""".trimIndent())
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@Test
|
|
230
|
+
fun `clean code passes`() {
|
|
231
|
+
lint()
|
|
232
|
+
.files(
|
|
233
|
+
kotlin("""
|
|
234
|
+
fun example() {
|
|
235
|
+
Logger.e("Something failed")
|
|
236
|
+
}
|
|
237
|
+
""").indented()
|
|
238
|
+
)
|
|
239
|
+
.issues(ForbiddenCallDetector.ISSUE)
|
|
240
|
+
.run()
|
|
241
|
+
.expectClean()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Suppressing Rules
|
|
249
|
+
|
|
250
|
+
```kotlin
|
|
251
|
+
// ✅ Suppress per call site when genuinely needed
|
|
252
|
+
@SuppressLint("GlobalScopeUsage")
|
|
253
|
+
fun legacyCode() {
|
|
254
|
+
GlobalScope.launch { /* unavoidable */ }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ✅ Suppress in lint.xml (project-wide baseline)
|
|
258
|
+
// lint.xml
|
|
259
|
+
<lint>
|
|
260
|
+
<issue id="GlobalScopeUsage" severity="ignore">
|
|
261
|
+
<ignore path="src/legacy/**" />
|
|
262
|
+
</issue>
|
|
263
|
+
</lint>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Anti-Patterns
|
|
269
|
+
|
|
270
|
+
- Writing rules that are too strict — rules with too many false positives get suppressed everywhere
|
|
271
|
+
- Not writing tests for lint rules — untested rules break silently on API changes
|
|
272
|
+
- One giant detector class — one detector per concern
|
|
273
|
+
- Severity `ERROR` for style issues — use `WARNING`; save `ERROR` for correctness issues
|
|
274
|
+
- Forgetting to register the rule in `IssueRegistry` — rule silently never runs
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Related Skills
|
|
279
|
+
- `detekt` — static analysis for Kotlin code style
|
|
280
|
+
- `gradle` — integrating lint into the build pipeline
|
|
281
|
+
- `convention-plugin` — sharing lint config across modules
|