Repository navigation
fix(js,ts): a call or new never lands on an interface or a type alias - #2801
Merged
Merged
Conversation
A call or `new` that a strategy resolved by name or through an import could target a TypeScript interface or type alias, which nothing can call or make an instance of. memfs's fs-core `result.ts` declares `interface Ok` beside `function Ok` (and `Err` beside `Err`), and `Ok(...)` through an import reached the interface. A `new` of a global that a project only declares a type for (`new Response()`, `new Worker()`, `new Event()`) was linked by name to that type, and jest's `write()` parameter call to a `type write`. Root cause: no rule on what a call may target. Whichever strategy found the name picked among same-named nodes by order, and nothing stopped it on a type. Fix (gateTargetKind): for the JS family, a `calls` or `instantiates` result whose target is an `interface` or a `type_alias` moves to the value the target's file declares under the same name (a function, class, component, constant or variable, first by position): `function Ok` beside `interface Ok`, a class merged with an interface. With no such value the result is dropped, and the reference stays unresolved. Validation: - __tests__/call-value-targets.test.ts (3 tests): all 3 fail on main and pass on the kernel. Each of 5 rules removed in turn fails at least one. - Sync equals a fresh index (CLI) when the value beside a type is added or removed, and when a project type for a global or a parameter appears. - A/B on 14 TS/JS repos and 2 controls: memfs's 11 Ok/Err calls move from the interfaces to the functions; 165 `new` of globals dropped (vite 27, nuxt 57, webdriverio 26, insomnia 47, jest 7, memfs 1) and jest's `write()` call on `type write`. Every dropped `new` names a global (Worker, Event, Response, Request, Headers, ReadableStream, Node's Module, mocha's Context/Suite) or a local variable. vben, outline, social-app, t3-turbo, nitro, vue3-antdv-admin, pathe, c12, flask and gin are byte-identical. - 91 related test files pass (2,178 tests). - Cost: fresh index within noise (median of 3, interleaved): vite 9.07 s to 8.74 s, nuxt 11.71 s to 10.93 s, insomnia 15.26 s to 15.11 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A call or
newthat a strategy resolved by name or through an import could land on a TypeScript interface or type alias. Nothing can call one, or make an instance of one.result.tsdeclaresinterface Ok<T>besidefunction Ok<T>(), and the same forErr.Ok(…)through an import reached the interface.newof a global that a project only declares a type for was linked to that type by name:new Response()to insomnia's Postmaninterface Response,new Worker()to vite's.d.tsinterface,new Event()to jest-circus'stype Event. jest'swrite(), a parameter, was linked to atype write.This is the half of #2800 that doesn't overlap #2754, split out as asked. #2800 stays open with the default-export half.
Root cause
The resolver has no rule on what a call may target. Whichever strategy found the name (an import, then name matching) picked among same-named nodes by order, and nothing stopped it on a type.
Fix
gateTargetKind(src/resolution/index.ts) now handles JS-familycallsandinstantiatesresults whose target is aninterfaceor atype_alias:function Okbesideinterface Ok, and a class merged with an interface.new Response()links nothing.Type positions (
references) are untouched:r: Ok<number>still names the interface.Validation
Tests:
__tests__/call-value-targets.test.tsindexes real temp repositories (3 tests).Ok/Errbeside their interfaces, called directly and through a barrel; a type position still names the interface.new Response(),new Event()andnew Worker()with only project types of those names, plus awrite()parameter call beside atype write: nothing links.new Store()reaches the class an interface merges with.Results:
newgated, a class beside an interface, and type aliases gated.Sync equals a fresh index (CLI
codegraph syncagainst a freshinit) in four cases:A/B: fresh
init, both arms with the native kernel, site-paired edge diff, main e8ddc4e.Ok(…)/Err(…)calls move frominterface Ok/Errto the functions.new ReadableStream()no longer lands on a project interface.new Response(),new Request(),new Headers()andnew Event()dropped; they had linked to Postman importer typesnew Response()dropped; it had linked to atype Responsein a build stubnew Worker()/new Event()dropped; they had linked to.d.tsinterfacesnewof globals dropped (Event,Response,Request, mocha'sContextandSuite)new Event(),new Module()and anew Reporter()of a local variable dropped, andwrite()ontype writeI grouped every dropped
new(165) by name and target. Each names a global (Worker,Event,Response,Request,Headers,ReadableStream, Node'sModule, mocha'sContext/Suite) or a local variable, never a class of the project. Each repo's removed set is the same as in #2800's A/B. #2800's other changes are absent here: jest's 23Resolveredges, outline's and insomnia's guessed-function calls, webdriverio's allure calls. They stay as on main and belong to the held default-export half.Cost: fresh index, median of 3, run back to back, alternating which arm goes first. All within noise:
The added work is one same-file lookup when a call's target is a type.
Full suite on fa70442 (
CODEGRAPH_KERNEL_EXPECT=1, 4 workers): 725 files (719 passed, 6 skipped), 8,688 tests: 8,606 passed, 0 failed, 82 skipped. No file needed a rerun.Not in this PR
function_refandreferencesin value positions (items.map(Ok)) aren't gated. None landed on an interface or type alias in these repos.🤖 Generated with Claude Code