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
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.
Summary
A TypeScript relative import that names the emitted extension (
import { x } from './util.js'forutil.ts, the formmoduleResolution: node16 | nodenext | bundlerrequires) never resolves through the import resolver.resolveRelativeImportappends 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
.tsfiles import with.jsspecifiers):renderDockStyles() { return renderDockStyles(); }in a class, withrenderDockStylesimported from./template.js, gets acallsedge 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...argspass-through. This is the shape TypeScript: a call throughthis.<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 isthis.field.method()).calls/importsedges wereresolvedBy: importand 15,419exact-match; with the remap below it is 7,312 and 12,784.fuzzydrops 44 → 23.Repro
codegraph initon those two files: thecallsedge fromSidebar::renderDockStylestargetsSidebar::renderDockStyles, nottemplate.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.jsfile still wins when both exist. PR incoming.