Skip to content

fix: don't flag Kotlin delegation/destructuring imports as unused - #676

Open
tallclouds wants to merge 1 commit into
peteromallet:mainfrom
tallclouds:fix/kotlin-delegation-unused-imports
Open

fix: don't flag Kotlin delegation/destructuring imports as unused#676
tallclouds wants to merge 1 commit into
peteromallet:mainfrom
tallclouds:fix/kotlin-delegation-unused-imports

Conversation

@tallclouds

Copy link
Copy Markdown

Problem

Kotlin resolves some imports by convention rather than by name, so the imported symbol never appears in the file body. The most common case is property delegation:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
...
var expanded by remember { mutableStateOf(false) }

getValue/setValue are required for by to compile, but neither name is ever written out. detect_unused_imports does a plain identifier search, so it reports both as unused in every Compose file.

$ desloppify scan --path .   # Compose Multiplatform project
...
Remove 73 unused issues
  - Unused import: getValue
  - Unused import: setValue

37 of those 73 findings were this false positive — over half the detector's output for that language. Acting on them breaks the build:

e: ... 'getValue' operator is required for delegation

Destructuring has the same shape: val (lat, lon) = point calls component1()/component2() without naming them.

Fix

Adds TreeSitterLangSpec.implicit_import_uses — a tuple of (name_pattern, body_pattern) pairs. When an import's simple name matches name_pattern and body_pattern is found in the file body, the import counts as used.

Kotlin declares two conventions:

  • getValue|setValue|provideDelegate, guarded on the by keyword
  • componentN, guarded on a destructuring declaration

The guards matter: an unused getValue import in a file with no delegation is still reported, so real dead imports aren't hidden. The (?<![\w.]) lookbehind keeps identifiers like nearby from masking them.

The field defaults to (), so every other language is unaffected. The lookup uses getattr so duck-typed spec stubs in the existing tests keep working.

Verification

  • New regression tests in desloppify/tests/lang/common/test_kotlin_unused_imports.py (5 cases, covering both the false positives and the negative cases that must still be flagged).
  • Full suite: 5816 passed, 4 skipped.
  • Against the original project: the 37 false positives drop to 0, while the 35 genuinely-unused imports in the same codebase are still reported.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PCWKUYMUFfgPFW9brzaPVn

Kotlin resolves some imports by convention rather than by name, so the imported
symbol never appears in the file body:

  import androidx.compose.runtime.getValue
  import androidx.compose.runtime.setValue
  ...
  var expanded by remember { mutableStateOf(false) }

The identifier search in detect_unused_imports never sees "getValue", so every
Compose file using property delegation is reported as having 2 unused imports.
Acting on the finding breaks the build. On a Compose Multiplatform project this
was 37 of 73 unused-import findings — over half the detector's output.

Adds a TreeSitterLangSpec.implicit_import_uses field: (name_pattern, body_pattern)
pairs declaring per-language conventions where a matching body pattern means the
import is used. Kotlin declares getValue/setValue/provideDelegate (guarded on the
`by` keyword) and componentN (guarded on destructuring declarations). Other
languages are unaffected — the field defaults to empty.

Genuinely dead delegation imports are still flagged: the guard requires the
convention's syntax to actually be present in the file.
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