Skip to content

TypeScript: calls through a re-exported namespace (import { z } of import * as z; export { z }) aren't followed #2951

Open

Description

@inth3shadows

Calls through a namespace that one module re-exports and another imports by name (import { z } from './index') aren't followed into the namespace. If any project symbol shares the member's name (an interface method, say), the call links to it by name. Otherwise it links nothing. Importing the namespace directly with import * as works.

// schemas.ts
export interface ZodString { ipv4(): this; }               // line 1
export function ipv4(): ZodString { return null as any; }  // line 2
// external.ts
export * from "./schemas.js";
// index.ts
import * as z from "./external.js";
export { z };
// index3.ts
export * as ns3 from "./schemas.js";
// index2.ts
import * as ns from "./schemas.js";
export { ns };

// b.ts:  import * as z from "./external.js";   z.ipv4();
// a.ts:  import { z } from "./index.js";       z.ipv4();
// c.ts:  import { ns } from "./index2.js";     ns.ipv4();
// d.ts:  import { ns3 } from "./index3.js";    ns3.ipv4();
caller edge on main expected
b.ts (import * as) ipv4 at schemas.ts:2 (resolvedBy: import) ✓ same
a.ts (import { z }, re-exported import * as + export { z }) ZodString.ipv4 at schemas.ts:1 (instance-method) ✗ schemas.ts:2
c.ts (same, one level) ZodString.ipv4 ✗ schemas.ts:2
d.ts (export * as ns3 from) ZodString.ipv4 ✗ schemas.ts:2

Without the interface, a/c/d get no edge at all, only b links.

Reproduced on main at 2577601 with CODEGRAPH_KERNEL=0. Linux/WSL2, Node 22.23.2. Not fixed by #2754 or #2800 (I ran this fixture on both heads). #2192 made export * as ns export the namespace itself, but calls through the imported name still aren't followed.

Where it shows up. zod's v4 package exports z exactly this way (src/v4/classic/index.ts: import * as z from "./external.js"; export { z }), and its tests write import { z } from "zod/v4". On zod (afdac65), 338 calls edges from z.<fn>() land on a same-named method rather than the exported function, by comparison with the TypeScript checker. For example, z.ipv4() in packages/zod/src/v4/classic/from-json-schema.ts links to the ipv4 method on the ZodString interface (schemas.ts:500) instead of export function ipv4 (schemas.ts:919), and z.enum([...]) lands on v3's ZodEnum method. import { z } from "zod" is the documented way to use the library, so any project that vendors or develops against a package built this way gets the same result.

Expected. When a named import resolves to a binding that the module created with import * as X or export * as X from, treat name.member() the same as a call through a direct namespace import.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions