ludo-technologies / ludo-technologies/polyscan

[BUG][auto] ts/local-binding-counted-as-cbo-dependency: local variables and parameters are counted as coupled classes

Open
#150 0 comments 0 reactions 0 assignees View on GitHub
auto-filed bug P1
Dominant language
Go
Stars
12
Forks
7
Avg merge
5h 46m
Merged PRs (30d)
49

Description

## Summary

For JS/TS, `cbo.classes[].metrics.dependent_classes` includes **local bindings** — function parameters, local `const`/`let` declarations, destructured variables and module-private consts — whenever a method is called on them. Any `obj.method()` call records `obj` as a coupled class without checking whether `obj` is a declared local rather than an imported name.

This inflates `coupling_count` on essentially every non-trivial JS/TS file and can flip `risk_level` to `high` on a file with ordinary coupling.

## Repro

`src/dep.ts`:
```ts
export class Widget {
render(): string { return 'w' }
}
```

`src/main.ts`:
```ts
import { Widget } from './dep'

const cache = new Map()

export function run(input: { load(): string }, label: string): string {
const w = new Widget()
cache.set(label, w.render())
return input.load()
}
```

```
polyscan analyze --select cbo --format json .
```

## Expected

`main.ts` has exactly one dependency (`./dep`), so `coupling_count: 1`.

## Actual

```
file=main.ts coupling_count=5 deps=Widget,cache,dep,input,w
```

`cache` (module-level const, line 3), `input` (function parameter, line 5) and `w` (local const, line 6) are not dependencies. (`Widget` is a separate defect — see the companion issue on constructor identifiers being counted alongside their module.)

## Real-world impact

In `rayriffy/elysia-rate-limit@ecb2515`, `src/services/plugin.ts` reports:

```json
{
"name": "plugin",
"metrics": {
"coupling_count": 15,
"dependent_classes": ["@types/Options","@types/Server","DefaultContext","Elysia","app","constants","defaultContext","defaultKeyGenerator","elysia","elysiaContextKeys","logger","nextReset","options","plugin","target"]
},
"risk_level": "high"
}
```

The file has 7 import statements. Six entries are locals: `elysiaContextKeys` (module const, L14), `options` (local const, L33), `app` (parameter, L44), `plugin` (local const, L53), `target` (parameter, L69), `nextReset` (destructured, L139). Note `plugin` — the module's pseudo-class name is derived from the filename, so `plugin.ts` ends up listed as **its own dependency**.

De-inflated coupling is 7, which is below the high threshold; `risk_level: "high"` is entirely an artifact. This was the only thing keeping the project's coupling score at 85/100.

## Suspected cause

`extractAttributeAccessDependencies` in `polyscan/internal/js/analyzer/cbo.go` (~L246-270) falls through to "count the object itself (could be a class instance)" and records any non-builtin receiver identifier, with no check against declared locals, parameters or module-level consts.

## Priority

P1 — a wrong metric on a construct present in nearly every JS/TS file (calling a method on a local variable), and it changes the reported risk level.

Found via the FP-audit skill in repo `rayriffy/elysia-rate-limit@ecb2515`.
polyscan version: `polyscan version 0.4.0`

Contributor guide

Open the contributing guide

Research direction

Start in polyscan/internal/js/analyzer/cbo.go, at extractAttributeAccessDependencies around lines 246-270, and reproduce the issue with the supplied src/dep.ts and src/main.ts example. Check the existing JS/TS dependency analysis and tests before changing the behavior. Done means local variables, parameters, destructured bindings, and module-private constants are absent from dependent_classes while the imported dependency remains counted.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, javascript, typescript
Domain
devtools, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.