ludo-technologies / ludo-technologies/polyscan
[BUG][auto] js/js-nested-function-declaration-dropped-or-duplicated: nested function declarations are reported twice (name, name_<line>) or not at all
- Dominant language
- Go
- Stars
- 12
- Forks
- 7
- Avg merge
- 5h 46m
- Merged PRs (30d)
- 49
Description
## Bug
In JavaScript, a named function declaration nested inside another function is reported wrongly by the complexity analysis:
- nested inside an **arrow function, function expression, method or IIFE** it is emitted **twice**, once as `name` and once as `name_`, with identical location and metrics;
- nested inside a **function declaration** it is **not emitted at all**.
## Repro
`b.js`:
```js
const arrow = () => {
function inArrow(x) { if (x) { return 1; } return 0; }
return inArrow(1);
};
class K {
m() {
function inMethod(x) { if (x) { return 1; } return 0; }
return inMethod(1);
}
}
function named() {
function inNamed(x) { if (x) { return 1; } return 0; }
return inNamed(1);
}
```
```
polyscan analyze --format json --select complexity . | jq -c '.complexity.functions[] | {name,start_line,end_line,cc:.metrics.complexity}'
```
## Expected
One entry each for `inArrow`, `inMethod` and `inNamed` (plus `anonymous_1`, `m`, `named`).
## Actual
```
{"name":"inArrow","start_line":2,"end_line":2,"cc":2}
{"name":"inArrow_2","start_line":2,"end_line":2,"cc":2}
{"name":"inMethod","start_line":7,"end_line":7,"cc":2}
{"name":"inMethod_7","start_line":7,"end_line":7,"cc":2}
{"name":"anonymous_1","start_line":1,"end_line":4,"cc":1}
{"name":"m","start_line":6,"end_line":9,"cc":1}
{"name":"named","start_line":11,"end_line":15,"cc":1}
```
`inNamed` is missing; `inArrow` and `inMethod` are duplicated. In the audited repo the duplicate shows up as `show` and `show_5` at `service/templates/analyze/report.js:5-13` (both `complexity: 3`), which also double-counts the function in `summary.total_functions`.
## Root cause (from reading `internal/js/analyzer/cfg_builder.go` and `internal/js/parser/ast.go`)
1. `CFGBuilder.processStatement` builds a nested function with a fresh `funcBuilder` and stores only `funcCFG` in `b.functionCFGs`; the nested builder's own `functionCFGs` (declarations inside that function) are discarded. The nested node is nevertheless appended to the block's `Statements`, so `BuildAll` marks its location as discovered and never rebuilds it. That drops `inNamed`.
2. `Node.Walk` visits the same child twice because `ast_builder.go` puts statements in both `Children` (via `AddChild`) and `Body` (e.g. lines 160-161). `BuildAll`'s discovery walk records `discoveredLocations` only before walking, so a function first found during the walk (nested in an expression) is visited again on the second pass, its name already exists in `allCFGs`, and it gets the `name_` suffix. That duplicates `inArrow` / `inMethod`.
## Priority
P1: wrong output (missing or duplicated function) on a common construct (nested function declarations), and it skews `summary.total_functions` and the complexity score.
- polyscan version: `polyscan version 0.3.3-1-g1b093ff`
- Found via the FP-audit skill in repo `ludo-technologies/pyscn@2c3d8ac4b3b6d8f1fb1790a47fcaab0079608017`
Contributor guide
Research direction
Start with internal/js/analyzer/cfg_builder.go, especially processStatement and BuildAll, then inspect internal/js/parser/ast.go and ast_builder.go for Node.Walk behavior. Run the provided polyscan analyze command against the b.js reproduction. Done means one entry each for inArrow, inMethod, and inNamed, with summary.total_functions no longer double-counting nested functions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, javascript
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100