fix(resolution): a receiver-less JS/TS call never binds to a method - #1735
Open
danusha2345 wants to merge 1 commit into
Open
fix(resolution): a receiver-less JS/TS call never binds to a method#1735danusha2345 wants to merge 1 commit into
danusha2345 wants to merge 1 commit into
Conversation
`serialize(this.raw)` inside `Record.serialize`, with a module-scope `function serialize` in the same file, resolved onto the method itself: both were exact-name candidates, both same-file, and findBestMatch's line-proximity term always prefers the enclosing method (colbymchenry#1714). In JS/TS a call written without a receiver cannot reach a method at all — methods need `this.`, an object, or a bound reference. The extractor emits `this.m()` and `super.m()` under the bare method name, so the receiver is read back from the call site's own line (the ref's column is the start of the call expression): when the text there begins with the name itself and nothing but whitespace, an operator or an opener precedes it, the call is bare, and `method` nodes leave the candidate set before ranking. matchFuzzy declines a lone `method` survivor for the same ref. `this.serialize()` (recursion) and `other.serialize()` are unchanged. Standalone on vite this removes 566 method-bound bare calls (`log(…)` onto a spec file's `log` method, `import(…)` onto a runner method, `resolve(…)` onto PluginContainer.resolve) and lets 274 previously out-ranked candidates through — `resolve` bound by `import { resolve } from 'node:path'` and Promise-callback `resolve` parameters — which colbymchenry#1715 and a local-binding rule are for; measured in the stack it is purely subtractive. Co-Authored-By: Claude Fable 5.1 <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.
Fixes #1714. Standalone off
main(b9ca4b7), one commit.What
Two rules for a receiver-less JS/TS call, both read from the call site's own source since the extractor emits
this.m()/super.m()under the bare method name:A bare call never binds to a method (the rule the issue proposes). The text at the ref's column is the call expression; when it starts with the name itself and nothing but whitespace, an operator or an opener precedes it,
methodnodes leave the candidate set before ranking, andmatchFuzzydeclines a lonemethodsurvivor.serialize(this.raw)insideRecord.serializeresolves to the module-scopefunction serialize;this.serialize()(recursion) andother.serialize()are unchanged.A name the file binds itself has no cross-file candidate. Once methods stop competing, the function that was out-ranked steps in — on vite,
resolve()insidenew Promise((resolve, reject) => …)landed onconfig.ts'sresolve. A parameter, aconst/let/var(destructuring included), afunctionorclassdeclared in the file shadows every other file's symbol of that name; none of these is a node the graph holds (a parameter, a const bound to a call result,const updateDiag = () => …), so this is the only way the matcher can know.const { x } = require('./m')/= await import('./m')binds an import, not a shadow, and is excluded; a parameter list has to consist of parameters, so a string argument containing the word does not match.Both memoised per context and cleared with the other name-matcher memos.
Measured
vitejs/vite
8492422and a 114-Kotlin / 42-Go / 96-JS Electron app, edge sets keyed withresolvedBy.Standalone against
main:vite's removals by target:
import(…)→module-runner/runner.ts::import264,test(…)→ a fixture'stest309,resolve(…)→PluginContainer.resolve158,transform(…)→PluginContainer.transform59,next(…)→bin/vite.js::next53,log(…)→ a spec'slog46, and the rest of that shape. The Electron app's:const updateDiag = () => shell.updateDiag(…),const logLine = (s) => …,const w = (...frames) => …— every sampled row a local arrow the graph has no node for. The 124 gained on vite are 80resolvebound byimport { resolve } from 'node:path'(the bare-import case #1715 declines) and 41log(…)ontoplayground/hmr-ssr/event.d.ts'sdeclare global { let log }, which is the right target; 3 are minified-library noise.Stacked on #1715 + #1718 + #1720 + #1732 (all four merged, this on top): vite LOST 522, GAINED 42 (the 41
logglobals + 1), the app LOST 103, GAINED 3 (minified). Thenode:pathpromotions are gone, as expected.Tests
__tests__/bare-call-no-method.test.ts, whole pipeline over source fixtures: the issue'sRecord.serialize→ freeserialize(self-edge gone, function edge present);this.serialize()keeps the self-edge;other.serialize()keeps the method; a Promise-executorresolve, a module-scopeconst transform = makeTransform()and a factory'sconst now = options.now || …produce no cross-file edge; a destructuredrequireand a string argument containing the name still resolve across files.resolution.test.ts,frameworks-integration,pr19-improvements,ts-this-field-call: 255 unchanged.tscclean.🤖 Generated with Claude Code