fix(js,ts): a callee that names nothing records no reference, and a wrapped callee is the plain call (#2742)

A TS/JS call whose callee was no name and no member access (an IIFE, a
curried `connect(mapState)(View)`, `handlers[type]()`, `(0, fn)()`, a
tagged template on a call) stored the callee's whole source text as the
reference's name. Nothing can answer such a name, so each one parked as a
failed reference, an IIFE's entire body among them. extractCall now reads
the callee past the wrappers that keep it the same callee (parentheses,
`!`, `as` / `satisfies`, `await`, type arguments) and records nothing when
that is not a name or a dotted chain; the calls inside are visited on
their own. Mirrored in the kernel (tsjs/extractors.rs callee_name_of).

The reference of a wrapped callee sits at its name, where a plain call's
does, and the resolver's bare-call check reads `fn!(`, `(fn)(`,
`(fn as F)(` and `(fn satisfies F)(` as it reads `fn(`: SigNoz's
`(callback as FrameRequestCallback)(0)` and `(callback)(0)` name the
arrow's own parameter, and from the `(` they bound a generated interface's
`callback` property.

tree-sitter-typescript parses a prefix operator over a generic call as it
parses `await f<T>()` (#2586): `!settings.get<boolean>('X')` and
`void apiRequest<T>()` are calls whose callee is `!settings.get` /
`void apiRequest`. The callee peel that handled `await` now peels a prefix
operator too (peelTsPrefixedCallee; the kernel's peel_await_callee), so the
call is to `settings.get`, and `void apiRequest<T>().then()` encodes as
`apiRequest().then`, not `voidapiRequest().then`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-21 11:01:53 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent aa01692620
commit ae0fc631e0
8 changed files with 211 additions and 19 deletions
+10 -2
View File
@@ -3628,10 +3628,12 @@ function isReceiverLessCall(ref: UnresolvedRef, context: ResolutionContext): boo
const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1]
?? context.readFile(ref.filePath)?.split('\n')[ref.line - 1];
if (line === undefined) return false;
// `^<name>\s*[(<]` at the call's column, without compiling a pattern per call.
// `^<name>\s*[(<]` at the call's column (or a JS/TS wrapper's tail after the
// name), without compiling a pattern per call.
if (!line.startsWith(ref.referenceName, ref.column)) return false;
CALL_OPENER.lastIndex = ref.column + ref.referenceName.length;
if (!CALL_OPENER.test(line)) return false;
JS_PEELED_CALLEE_TAIL.lastIndex = CALL_OPENER.lastIndex;
if (!CALL_OPENER.test(line) && !(JS_FAMILY.has(ref.language) && JS_PEELED_CALLEE_TAIL.test(line))) return false;
// Nothing but whitespace, an operator or an opener may precede a bare call:
// read the text before the column backwards, past trailing whitespace,
// instead of end-anchoring a pattern that scans the prefix from its start.
@@ -3649,6 +3651,12 @@ function isReceiverLessCall(ref: UnresolvedRef, context: ResolutionContext): boo
/** `\s*[(<]` from a given index (sticky) — an optional call's `?.(` too. */
const CALL_OPENER = /\s*(?:\?\.\s*)?[(<]/y;
/**
* What follows a JS/TS callee's name inside a wrapper the extractor sees
* through — `fn!()`, `(fn)()`, `(fn as F)()`, `(fn satisfies F)()` — whose
* reference sits at the name, as a plain call's does (sticky).
*/
const JS_PEELED_CALLEE_TAIL = /\s*[!)]|\s+(?:as|satisfies)(?![\w$])/y;
const WHITESPACE = /\s/;
const WORD_CHAR = /\w/;
/** A character that ends a receiver: `.`, a word character, `$`, `]` or `)`. */