Repository navigation
fix(ts): a receiver a dependency-injection call sets up has the class it is given - #2661
Merged
Merged
Conversation
Angular's `private readonly dataService = inject(DataService)` and vscode's `this._register(this.instantiationService.createInstance(Widget, …))` or `const widget = this.instantiationService.createInstance(Widget)` gave the field or local no type, so calls through them linked nowhere or by name alone. The field reader and the TS local patterns now read them: for a field, `inject` only when the file imports it from `@angular/core` (Vue's `inject(key)` returns whatever was provided under the key), and `createInstance` only when the project declares one that returns an `InstanceType` of its constructor argument; a local's call is still validated against the class's methods. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… instance vscode's service = instantiationService.createInstance(TestServiceAccessor).filesConfigurationService was read as a TestServiceAccessor, hiding the local's own annotation above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt leads to the class
bitwarden's inject<AdvancedUriOptionDialogParams>(DIALOG_DATA) has the type argument's type, and its MessagingService (export { MessageSender as MessagingService }) is the class the import renames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
colbymchenry
added a commit
that referenced
this pull request
Oct 22, 2026
…parent (#2818) A TS/JS call written super.run() is recorded under its bare name, as issue #2497 found. Inside an override of run every strategy found the calling method itself, and the self-edge gate declined it, so the call was unlinked; elsewhere it went to whatever method had the name, often the class's own or a sibling class's in the same file, and, for a parent outside the project like HTMLElement, Set or Promise, to a project namesake. A call written on super where it starts is now resolved through the class's resolved extends edges, which the resolver persists before it resolves calls: the nearest ancestor's method of that name, a static one for a call in a static member, or nothing when no ancestor in the project has it. The call may sit in a method, a class field, or an arrow function inside either. One the index can't place in a class (a class expression's method, an object literal's) keeps the old path, as does one after a class keyword in the member, outside comments and strings. The self-edge gate now reads a TS/JS call at its own column, so a this.run() on the line of a super.run() keeps its edge. The issue's first part, a method call on the receiver of an aliased constructor, already resolves on main since PR #2661 and is unchanged. Validation: the new test fails 3 of 4 on main 68fcf2f and passes 4 of 4 on the fix; the fourth pins the class-expression guard, and a mutation of each guard fails a test. A/B against main on 22 repos (20 TS/JS, flask and gin): 660 call sites change, all super calls, checked against each class's extends chain and sampled by hand. 643 gain an edge to an ancestor's method (304 in three.js, like super.copy(source) reaching Object3D.copy; 210 in webpack, 85 in lit); 2 move off the class's own method to the parent's; 4 keep their target, now by the parent; 11 lose a guess, where the parent is a builtin or a library's class, like webpack's SortableSet extending Set, or a mixin's parameter. Nodes and all other edges are unchanged; 14 repos' dumps, the controls among them, are byte-identical. A sync that moves a parent, swaps a parent or adds an override to an intermediate class matches a fresh index. Index time, median of 3 interleaved runs on three.js, webpack, jest and insomnia: +1.7% total, noise; 7 interleaved rounds on three.js alone: -1.5%. Fixes #2497 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What was wrong
A TypeScript field or local set from a dependency-injection call had no type, so calls through it linked nowhere, or to whichever class had a method of that name:
#2647 read
this._register(new X())fields and #2656 made a known class's call walk its supertypes, but neither covered an injector. A census on main counted 410createInstancefields and 764 locals in vscode, 2,351inject(X)fields in bitwarden and 587 in ghostfolio.A related gap: a type the file imports under another name was looked up by its local name. bitwarden writes
import { OrganizationApiServiceAbstraction as OrganizationApiService }andexport { MessageSender as MessagingService }, and vscode writesimport { Engine as FileSearchEngine }. Each call through such a value reached a class that happened to have the local name.What changed (
src/resolution/name-matcher.ts)x = inject(X)andx = inject<T>(TOKEN). The type argument wins when there is one.x = this.….createInstance(X, …), alone or inside a pass-through wrapper (this._register(…),this._store.add(…)).injectmust be imported from@angular/core. Vue'sinject(key)returns whatever was provided under the key.createInstancemust be declared somewhere in the project withInstanceType<in its signature, as vscode'sIInstantiationServicedeclares it.const x = inject(X),inject<T>(…)and….createInstance(X, …).createInstance(Accessor).widget) isn't read as the class.tsKnownTypeCallnow finds a type the file imports under another name through that import (tsNamedType), when the name isn't declared under itself in the imported file.Tests
__tests__/ts-injected-receivers.test.ts(5 tests):inject(X)fields and locals, includinginject(X, { optional: true });inject<T>(TOKEN);injectis not read;createInstancefields, wrapped fields and locals, and a member of the created instance;createInstancethe project doesn't declare withInstanceTypeis not read.__tests__/ts-known-class-methods.test.tscovers the renaming import.A/B: fresh index, main 383c2cd vs this branch (since then, main has only gained #2660, which changes Go imports)
FileSearchEngine::search→Engine::search(32);IJSONEditingService::write→JSONEditingService::write(20);AskAgentProvider→PlanAgentProvider::provideCustomAgents(19);IClaudeAgentSdkService::query→ the created marketplace services'query(28).inject(X)fields.I18nService::tbecomes the importedTranslationService::t,ElectronMainMessagingService::sendbecomesMessageSender::sendthrough the renaming re-export, concrete services become the abstractions their files import under the concrete name, andinject(BrowserRouterService)reaches the browser router.router.navigateon Angular'sinject(Router), which had been guessed as a project class'snavigate.inject(X)fields.🤖 Generated with Claude Code