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
TypeScript imports naming the emitted .js extension never resolve through the import resolver (wrapper methods become self-edges) #1705
A TypeScript relative import that names the emitted extension (import { x } from './util.js' for util.ts, the form moduleResolution: node16 | nodenext | bundler requires) never resolves through the import resolver. resolveRelativeImport appends each candidate extension to the specifier (util.js.ts, util.js.tsx, …) and then tries the specifier as-is (util.js), and none of those files exist, so it returns null. Every name imported that way then falls through to bare-name matching.
Two visible effects on a real repo (Chrome MV3 extension, 582 files, 92 of its .ts files import with .js specifiers):
A method that wraps the same-named function it imports resolves to itself.renderDockStyles() { return renderDockStyles(); } in a class, with renderDockStyles imported from ./template.js, gets a calls edge from the method to the method (resolvedBy: exact-match, confidence 0.4). Eight such false self-edges here: three dock-template wrappers, four static wrappers on a consensus engine, one ...args pass-through. This is the shape TypeScript: a call through this.<field> resolves to the ENCLOSING method when the two share a name — silent self-edge, 0% recall on that shape #1496 does not cover (that one is this.field.method()).
Cross-module edges are name guesses. On this tree 4,002 calls/imports edges were resolvedBy: import and 15,419 exact-match; with the remap below it is 7,312 and 12,784. fuzzy drops 44 → 23.
codegraph init on those two files: the calls edge from Sidebar::renderDockStyles targets Sidebar::renderDockStyles, not template.ts::renderDockStyles. Same for .jsx → .tsx, .mjs → .mts, .cjs → .cts, and for an aliased specifier (@/lib/util.js).
Fix
When the specifier ends in an emitted extension and no file with that exact name exists, retry with the source extensions TypeScript would have compiled from (.js → .ts, .tsx, .d.ts; .jsx → .tsx; .mjs → .mts; .cjs → .cts), for the TS/JS-family languages only. A real .js file still wins when both exist. PR incoming.
Before:resolveImportPath('./util.js', 'shared/engine.ts', 'typescript', …) → null (resolver appended .ts to .js, never remapped).
After: → shared/util.ts
__tests__/import-emitted-specifier.test.ts 7/7 pass, including the wrapper-method e2e (Sidebar.renderDockStyles → template.ts::renderDockStyles, not self).
Lands the approach from #1706 (thanks @bompus), rebased onto current main (CHANGELOG-only conflict). Re-index after upgrading.
Activity
fix(resolution): an import naming the emitted .js extension resolves …
colbymchenry commentedon Sep 8, 2026
Fixed in #1767 (merge
28033f6).Verified on Linux:
resolveImportPath('./util.js', 'shared/engine.ts', 'typescript', …)→null(resolver appended.tsto.js, never remapped).shared/util.ts__tests__/import-emitted-specifier.test.ts7/7 pass, including the wrapper-method e2e (Sidebar.renderDockStyles→template.ts::renderDockStyles, not self).Lands the approach from #1706 (thanks @bompus), rebased onto current
main(CHANGELOG-only conflict). Re-index after upgrading.