Skip to content

Add a type-resolving NamespaceImport lint check - #3123

Closed
StylianosGakis wants to merge 1 commit into
chore/namespace-import-ktlintfrom
chore/namespace-import-lint-check
Closed

Add a type-resolving NamespaceImport lint check#3123
StylianosGakis wants to merge 1 commit into
chore/namespace-import-ktlintfrom
chore/namespace-import-lint-check

Conversation

@StylianosGakis

Copy link
Copy Markdown
Member

Stacked on #3122. Review that one first.

The ktlint rule in #3122 already enforces the import rule on every source set, so this adds precision and IDE feedback rather than coverage. It is entirely optional: nothing regresses if it is dropped.

What it adds over the ktlint rule

Type resolution. It resolves the owner through context.evaluator.findClass instead of inferring one from the shape of the import path. platform.Foundation.systemLocale therefore needs no prefix exception to stay unflagged, whereas the ktlint rule carries CAPITALIZED_PACKAGES for exactly that case.

Inline IDE reporting. Android Lint surfaces the problem while the import is being typed, which is where a style rule is cheapest to obey. Catching it at CI is much later.

Deny and allow lists are configurable through the shared lint.xml, alongside the existing ComposeM2Api.

The coupling this introduces

Two mechanisms now encode the same policy, and Android Lint cannot run on KMP modules, so the ktlint rule stays the mechanism of record. The default denied list (kotlin.time.Clock.System) exists in both and has to be kept in step. Both files say so in their KDoc.

If that bookkeeping is judged not worth the IDE benefit, the right move is to close this PR and keep #3122 alone. Driving both from one shared configuration, for instance .editorconfig, is the alternative worth considering, and is deliberately not attempted here.

On Android modules a violation is now reported twice, which only happens when someone is already wrong.

Test source set

hedvig-lint had no test source set, so this adds one plus the lint-tests dependency. Seven cases cover both directions of the rule.

Note that these tests need an Android SDK: they fail locally with "This test requires an Android SDK: No SDK configured" unless ANDROID_HOME is set. CI's common-setup provides it.

Verification

  • 7/7 detector tests pass.
  • Positive control on an Android library module: planting import hedvig.resources.Res.string in CrossSells.kt fails :cross-sells:lint with Error: Import Res and write Res.string at the use site ... [NamespaceImport]. Reverted, lint clean.
  • ./gradlew lint across the repo passes.

The ktlint rule already enforces the import rule on every source set, so
this adds precision and IDE feedback rather than coverage.

Two things it does that ktlint cannot. It resolves the owner through
context.evaluator.findClass instead of inferring one from the shape of the
import path, so platform.Foundation.systemLocale needs no prefix exception
to stay unflagged. And Android Lint reports inline in the IDE while the
import is being typed, which is where a style rule is cheapest to obey.

It cannot run on KMP modules, so the ktlint rule stays the mechanism of
record and the two denied lists have to be kept in step. Deny and allow
lists are configurable through the shared lint.xml.

hedvig-lint had no test source set, so this adds one plus the lint-tests
dependency. Seven cases cover both directions of the rule.
@StylianosGakis
StylianosGakis force-pushed the chore/namespace-import-lint-check branch from a933462 to 3cc093c Compare September 3, 2026 12:36
@StylianosGakis

Copy link
Copy Markdown
Member Author

Closing: not worth its complexity for an import style rule.

It was only ever additive. #3122's ktlint rule enforces the same policy on every source set, so nothing is left unenforced by dropping this.

What it bought, and why that wasn't enough:

  • Type resolution. Measured: the only case where the ktlint heuristic needs its CAPITALIZED_PACKAGES escape hatch is platform.*, which appears in 8 files, every one in nativeMain. There are zero other capitalized-package member imports in the repo. So the precision gain is worth ~nothing in practice, and the source sets where it would matter are ones Android Lint cannot reach anyway.
  • Inline IDE feedback. Real, on the 106 of 140 modules where Android Lint runs, but it costs a second implementation of the same policy with a duplicated denied list that nothing tests for agreement.

The 34 modules it cannot reach are all KMP, including design-system-hedvig, which is where 19 of the 26 violations lived. Since the repo keeps moving toward KMP, that blind spot only grows.

Worth reopening if AGP issue 246751841 is ever fixed. At that point Android Lint could cover the whole repository and would be the better single mechanism, making the ktlint rule the redundant half instead. The code here still works and is fully tested if that day comes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant