TL;DR for detekt
If you’d like to just see the implementation and move forward quickly, you can just check my PR. Otherwise bear with me a couple of minutes for some decision points.
The story
Speaking from an Android background, we have a couple of options like detekt, ktlint on top of Android’s lint tool for static code analysis and more (pmd, checkstyle etc…). Code formatters like Spotless work well with ktlint
.
I’m not gonna compare ktlint
and detekt
in this article (it has been done many times in the past in different articles like in Medium and in ChatGPT), but both tools can work for Kotlin Multiplatform projects and allow analysis of platform-specific code (JVM, JS, Native).
I’m more used to working with detekt
and like their suggestion to tweak the rules to work with Google’s Compose API Guidelines.
But the problem is by default, Detekt
looks for Kotlin and Java source code within a standard Gradle project as mentioned here. You can probably see these discussions when you Google how to make Detekt
work for a KMP project (by the time of writing);
- how are you using Detekt on multiplatform projects…
- If I wanted to run detekt tasks on all KMP source …
- Detekt isn’t able to detect issues on
iosMain
source set https://github.com/detekt/detekt/issues/5609 - Running detekt on JVM target (in KMP project) doesn’t run against the common source set https://github.com/detekt/detekt/issues/7073
- There is no
detektCommonMain
task but there isdetektMetadataMain
as mentioned here
For the first 2 issues, I agree that it could be nice to have a common task with no configuration needed. For the 3rd and 4th issues, it looks like a source set issue to me.
For the last one, it looks like a nice suggestion but it usually gave issues for generated sources to me (although I know I can ignore paths). It’s interesting that a small task like implementing Detekt
can be such a headache at least by the time of writing (maybe there is a nicer solution but Google search makes me think that community is having the same trouble).
Looking at the KMP samples in jetbrains here, none of them had detekt
configured (Maybe I missed some, let me know if there is a nice example please).
DroidconKotlin example has a nice setup for ktlint
but they only support for iOS and Android atm.
Even kotlinconf-app didn’t have any static code analysis tool which surprised me a lot.
Considering all these, I could’ve also chosen to go with separate static code analysis for separate platforms (Swift lint for Swift) like mentioned in this nice article. But considering that I’m planning on writing almost no Swift, I decided to stay with one static analysis tool on top of the Lint.
At the end, I decided to include all the source sets myself by using source.setFrom(...)
configuration feature of Detekt
for each sub-Gradle project. This reports the issues multiple times but so far has looked pretty reliable.
If you’d like to see how I implemented this, you can check the PR.
Little remark
- name: Check detekt
run: ./gradlew detekt
- name: Check lint
run: ./gradlew lintDebug
I could have just used ./gradlew check
task in CI since detekt
and lint
tasks also run when check
task runs, but IMO the check
task does a lot. But if you want to follow standard Gradle lifecycle practices, and you’re OK with less fine-grained control, then the check
task is not bad.
I wanted to optimize CI time and only run certain checks in certain situations like running lint only on debug build type, therefore I separated detekt
and lint
tasks and run only them which saved me some time. I believe it also gives faster feedback and better visibility into where a failure happened.
But that might be just how I eat the yogurt.
One last suggestion, I came across a nice article here which mentions nice tools to have for a KMP project.
We can now tick one/two checks in the list below and rest remains 🎉;
- Core
- ✅ Dependency management: Renovate
- ✅ Lint
- ✅ Static code analysis: Detekt
- Logging
- Error reporting
- Analytics
- Tracing
- Network
- Benchmarking
- Build conventions
- Flavours
- Mocks
- Test fixtures
- Build info
- Preferences
- Storage
- DI
- Feature flags (local & remote)
- Deep linking
- Push notifications
- TimeProvider
- Local Formatters
- Coroutine Dispatchers & Test helper
- Unit testing
- Test coverage
- Obfuscation & Shrinking
- Pipelines
- Releasing
- Force updates
- UI
- Design system
- Gallery App
- Navigation
- Baseline profiles
- Compose compiler metrics
- Previews
- Network image loading
- supportsDynamicTheming
- Status bar color changing
- App settings with Resource Environment
- l10n
- i18n
- Testing
- UI Testing
- Compose Screenshot testing
Hope to see you in the next episode.
Until then may the force be with you… 🖖