Repository navigation
Description
If a function declares a local that shadows a name the file imports from a project file, a call to that name from a nested arrow links to the import instead of the local. The same code importing from a package resolves correctly in every shape, thanks to #2855.
// lib.ts
export function skipString(j: number): number { return j + 1; }
// use.ts
import { skipString } from './lib';
export function scanA(s: string) {
const skipString = (j: number): number => j + 2;
return skipString(0); // line 4
}
export function scanB(s: string) {
function skipString(j: number): number { return j + 3; }
return skipString(0); // line 8
}
export function scanC(s: string) { return skipString(0); } // line 10
export function scanD(s: string) {
const skipString = (j: number): number => j + 2;
const skipValue = (j: number): number => skipString(j); // line 13
return skipValue(0);
}
export function scanE(s: string) {
function skipString(j: number): number { return j + 3; }
const skipValue = (j: number): number => skipString(j); // line 18
return skipValue(0);
}| call | from './lib' (project) |
from 'some-pkg' (package) |
expected |
|---|---|---|---|
line 4 (scanA, own local) |
no edge | local at line 3 | local |
line 8 (scanB, own local function) |
no edge | local at line 7 | local |
line 10 (scanC, no shadow) |
lib.ts skipString |
none (package) | the import |
| line 13 (nested arrow, const shadow) | lib.ts skipString (resolvedBy: import) ✗ |
local at line 12 | local |
| line 18 (nested arrow, function shadow) | lib.ts skipString (resolvedBy: import) ✗ |
local at line 17 | local |
The two marked rows are wrong edges. The two "no edge" rows for the project import are missing edges rather than wrong ones. The import resolver appears to check for a shadow only in the calling node's own scope, not in the functions around it, and only the package-import path (#2855) prefers the local.
Reproduced on main at 31ec908 (6162d872 since then only touches the watcher), with CODEGRAPH_KERNEL=0. Linux/WSL2, Node 22.23.2.
In this repo. src/resolution/frameworks/react.ts has the same shape. readObjectValue declares const skipString (lines 392–396), and the nested skipValue arrow calls it at line 401. That call links to skipString in object-literal.ts, which react.ts imports. The same shape occurs again at line 441, against a second local skipString declared at line 432. I found these by checking every TS calls edge against the TypeScript checker.
Expected. Apply the same lexical shadow check to a name imported from a project file as to one imported from a package: the innermost binding in scope at the call wins, including bindings in enclosing functions.
Edited 2026-11-04: re-checked on main at f72c9d2 (with CODEGRAPH_KERNEL=0 and with the native kernel, Node 22.22.2): still reproduces. Added: scanA and scanB get no edge at all, to the local or anywhere, which is a separate gap. Nothing else changed.
Activity