ludo-technologies / ludo-technologies/polyscan
[BUG][auto] ts/unused-import-misses-extends-implements-and-decorator-usage: imports used in a heritage clause, type position or decorator are reported unused
- Dominant language
- Go
- Stars
- 12
- Forks
- 7
- Avg merge
- 5h 46m
- Merged PRs (30d)
- 49
Description
`DetectUnusedImports` decides an import is unused by walking the AST for `NodeIdentifier` references. Identifiers that appear in a class heritage clause (`extends` / `implements`), in a type position, or in a decorator are never built into the AST at all, so those imports are reported as never used.
`extends` and decorators are value positions, so this is not a type-awareness gap: the imported binding is genuinely referenced at runtime.
## Repro
Five files, no config:
`base.ts`
```ts
export class Base {}
export interface Iface { m(): void; }
export type Res = { n: number };
export function Deco(): ClassDecorator { return () => {}; }
export function called() { return 1; }
```
`a.ts` — `import { Base } from './base'; export class A extends Base {}`
`b.ts` — `import { Iface } from './base'; export class B implements Iface { m() {} }`
`c.ts` — `import { Res } from './base'; export function c(): Res { return { n: 1 }; }`
`d.ts` — `import { Deco } from './base'; @Deco() export class D {}`
`e.ts` — `import { called } from './base'; export function e() { return called(); }`
```
polyscan analyze --select deadcode --format json .
```
## Expected
No `unused_import` findings. Every imported name is referenced.
## Actual
Four of the five are reported unused; only the plain call in `e.ts` is recognised.
```
a.ts -> Imported name 'Base' from './base' is never used
b.ts -> Imported name 'Iface' from './base' is never used
c.ts -> Imported name 'Res' from './base' is never used
d.ts -> Imported name 'Deco' from './base' is never used
```
The specifier extension is not involved: `./base` and `./base.js` behave identically, and #126 resolves correctly here.
## Cause
`buildClassDeclaration` (`polyscan/internal/js/parser/ast_builder.go:308`) reads only the `name` and `body` fields of the tree-sitter `class_declaration` node. The `class_heritage` child holding `extends` / `implements`, and the `decorator` children, are never visited, so `buildIdentifier` never runs for them. `DetectUnusedImports` (`polyscan/internal/js/analyzer/unused_code.go:157-180`) then finds no reference. `type_identifier` is already mapped to `NodeIdentifier` at `ast_builder.go:125`, so the gap is the missing traversal, not the node mapping.
## Impact
On `rogerpadilla/uql`, **all 91 of 91 `unused_import` findings are false positives** — 52 from `extends`/`implements`, 39 from type positions and decorators. `unused_import` is 23% of that run's 390 dead-code findings and feeds the dead-code penalty.
## Priority
P1 per the rubric: a wrong finding on a common construct. Every subclass, every interface implementation and every decorated class in a TypeScript project is affected, but not every file, so not P0. The fix looks local to `buildClassDeclaration`, hence `good first issue`.
polyscan version `0.4.0` (`be31afc`).
Found via the FP-audit skill in repo `rogerpadilla/uql@6252e1f`.
Contributor guide
Research direction
Start by reading buildClassDeclaration in polyscan/internal/js/parser/ast_builder.go around line 308, then inspect DetectUnusedImports in polyscan/internal/js/analyzer/unused_code.go around lines 157-180. Run the five-file reproduction with polyscan analyze --select deadcode --format json . and trace the class heritage, type, and decorator nodes. Done means all five imports produce no unused_import findings while the existing plain call remains recognised.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, typescript
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100