You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(indexer): every JS/TS ES import collapses to a bare module row — named, default and namespace bindings are all lost #1526
tools/languages/javascript.py:436 and tools/languages/typescript.py:413
Import handling starts with:
node.child_by_field_name('import')
import_clause is an unnamed child of import_statement, not a field, so this always returns None and every ES import falls into the bare-module fallback (js :437-440, ts :414-417).
The entire named-import block below it (js :456-465, ts :428-437) is therefore dead code — and would still not work if the lookup were fixed, because it tests the clause against identifier / named_imports / namespace_import, which are children ofimport_clause, not the clause itself.
One row for the module. useState, the alias us, and useEffect are all lost. Default and namespace (import * as X) imports collapse the same way.
Impact
Beyond the missing binding data, this actively corrupts call resolution. resolution/calls.py:2173-2177 builds:
local_imports= {
imp.get("alias") or (imp.get("name") or"").split(".")[-1]: imp.get("name")
...
}
With alias=None and a relative specifier like ./components/Button, the key becomes "/components/Button" — splitting on . cuts the path, not a module chain. So the identifier Button never maps to its import, and no call to a named ES import can be resolved cross-file. Affects .js, .ts and .tsx — the largest language group in most indexed repos.
Suggested fix
Walk to import_clause as an unnamed child, then read its children (identifier for default, named_imports → import_specifier with optional alias, namespace_import) to emit one row per binding.
Important finding: this cannot be fixed at the parser alone. I implemented the parser fix, measured it, found it regresses call resolution, and backed it out rather than ship it. Recording the evidence so whoever picks this up doesn't repeat the work.
The parser fix itself is straightforward
import_clause is an unnamed child, not a field, so child_by_field_name('import') is always None. Walking named_children for the clause, then its children for identifier / namespace_import / named_imports, extracts every binding correctly:
name must stay the module.writer.py:593 does MERGE (m:Module {name: row.name}), so putting the binding in name collapses every default import in the repo into one Module node called default. The binding belongs in imported_name + alias, both of which the writer already reads (:566).
namespace_import has no alias field — the local name is a plain identifier child. The existing code's lookup would have failed even if it had been reachable.
Why it regresses
Emitting one row per binding — with or without also keeping the original module-level row — costs precise call edges on the TypeScript fixture:
main -> with binding rows
CALLS 389 -> 240 (-149)
HEURISTIC_CALLS 0 -> 122 (+122)
edges TOTAL 1489 -> 1462 (-27)
Confirmed this is deterministic (two identical regeneration runs produce identical counts, so it is not golden nondeterminism), and confirmed by elimination: emitting only the module row through the same new helper leaves the golden passing unchanged. So the binding rows themselves are what break it.
The likely mechanism is resolution/calls.py:1776 and :2174, which build:
That is a dict comprehension over the import rows, so multiple rows per statement change which key wins and what each maps to. wildcard_imports just below iterates the same list.
What this issue actually needs
The parser change and the resolution update in one PR, with the golden delta checked to be additive. Anyone attempting it should regenerate sample_project_javascript and sample_project_typescript and compare edge counts by type — a rise in HEURISTIC_CALLS against a fall in CALLS is the signal that resolution has lost the mapping.
Leaving this open and unassigned. The underlying bug is real — named ES imports carry no binding data at all today — but the fix is bigger than it looks.
Activity
github-actions commentedon Aug 1, 2026
Hi! 👋 Join our CodeGraphContext Discord channel to collaborate: https://discord.gg/dR4QY32uYQ
Shashankss1205 commentedon Aug 5, 2026
Important finding: this cannot be fixed at the parser alone. I implemented the parser fix, measured it, found it regresses call resolution, and backed it out rather than ship it. Recording the evidence so whoever picks this up doesn't repeat the work.
The parser fix itself is straightforward
import_clauseis an unnamed child, not a field, sochild_by_field_name('import')is alwaysNone. Walkingnamed_childrenfor the clause, then its children foridentifier/namespace_import/named_imports, extracts every binding correctly:Two things worth knowing for the implementation:
namemust stay the module.writer.py:593doesMERGE (m:Module {name: row.name}), so putting the binding innamecollapses every default import in the repo into oneModulenode calleddefault. The binding belongs inimported_name+alias, both of which the writer already reads (:566).namespace_importhas noaliasfield — the local name is a plainidentifierchild. The existing code's lookup would have failed even if it had been reachable.Why it regresses
Emitting one row per binding — with or without also keeping the original module-level row — costs precise call edges on the TypeScript fixture:
Confirmed this is deterministic (two identical regeneration runs produce identical counts, so it is not golden nondeterminism), and confirmed by elimination: emitting only the module row through the same new helper leaves the golden passing unchanged. So the binding rows themselves are what break it.
The likely mechanism is
resolution/calls.py:1776and:2174, which build:That is a dict comprehension over the import rows, so multiple rows per statement change which key wins and what each maps to.
wildcard_importsjust below iterates the same list.What this issue actually needs
The parser change and the resolution update in one PR, with the golden delta checked to be additive. Anyone attempting it should regenerate
sample_project_javascriptandsample_project_typescriptand compare edge counts by type — a rise inHEURISTIC_CALLSagainst a fall inCALLSis the signal that resolution has lost the mapping.Leaving this open and unassigned. The underlying bug is real — named ES imports carry no binding data at all today — but the fix is bigger than it looks.
fix: eight open issues across jobs, watcher, validator, simulator, MC…