Repository navigation
Description
Follow-up to #2589. A method call on an untyped local initialized from a library call still links to the project's only method with that name. #2811 fixed this for a class component's render() and lifecycle methods, but an ordinary class method is still guessed.
// app.ts
export class App { render(): void {} translate(x: number): void {} close(): void {} }
// use.ts
import { createRoot } from 'react-dom/client';
import * as net from 'net';
import { createServer } from 'net';
export function a(el: HTMLElement) { const root = createRoot(el); root.render(null); }
export function b() { const s = net.createServer(); s.close(); }
export function c() { const s = createServer(); s.close(); }
export function d(canvas: HTMLCanvasElement) { const ctx = canvas.getContext('2d')!; ctx.translate(1); }
export function e(canvas: HTMLCanvasElement) { canvas.getContext('2d')!.translate(1); }| function | edge on main |
expected |
|---|---|---|
a |
App.render (instance-method) |
none |
b |
App.close |
none |
c |
App.close |
none |
d |
App.translate |
none |
e (chained, no local) |
none | none |
Renaming the locals doesn't change anything, so this isn't the variable-name guess in #2946. The chained form e already links nothing; only the assigned local takes the guess. In each case the local's value comes from something the project doesn't declare: a package import, a node: namespace, or a method on a DOM-typed parameter. That fits #2169's "an outside type is not a project type" rule, but the type is never written down, so the rule doesn't fire.
Reproduced on main at 31ec908 (6162d872 since then only touches the watcher), with CODEGRAPH_KERNEL=0. Linux/WSL2, Node 22.23.2.
Scale (found by checking every TS calls edge against the TypeScript checker). On excalidraw (14bb42e), 63 instance-method edges land on project code where tsc says the method is a library one. For example, every context.translate(…) on a canvas.getContext("2d")! local, as in packages/element/src/renderElement.ts, links to App.translate in packages/excalidraw/components/App.viewport.ts. On this repo there are 58. For example, server.close() on net.createServer(…) in __tests__/cli-unlock.test.ts links to the close() of the viewer's server interface in src/ui-server/index.ts. The interface method gets callers it never has.
Possible direction (not verified). When the local's initializer is a call whose callee resolves outside the project (an import from a package or node:, or a member of a receiver already typed as an outside type), treat the local as outside-typed: the same "no guess" outcome as an annotated const r: Root = createRoot(el) in #2589. #2152 has PHP's version of this ("an untyped receiver keeps a unique method only when named for it").
Related: #2589, #2811, #2169, #2946.
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. Related: #3036 (closed as covered by this issue and #2948). Nothing else changed.
Activity