mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-26 04:19:33 +08:00
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:
co-authored by
Claude Opus 5.5
parent
aa01692620
commit
ae0fc631e0
@@ -559,13 +559,17 @@ const TS_JS_TRANSPARENT_RECEIVER_TYPES = new Set([
|
||||
|
||||
/**
|
||||
* tree-sitter-typescript parses `await f<T>(x)` as a call whose callee is
|
||||
* `await f` (#2586); the call is to `f`. Peels every `await`, skipping a
|
||||
* comment written after one. Mirrored in the kernel (tsjs/extractors.rs).
|
||||
* `await f` (#2586), and a prefix operator over a generic call the same way:
|
||||
* `!settings.get<boolean>('X')`, `void apiRequest<T>()`, `typeof load<T>()`.
|
||||
* The call is to `f`. Peels every `await` and prefix operator, skipping a
|
||||
* comment written after an `await`. Mirrored in the kernel (tsjs/extractors.rs).
|
||||
*/
|
||||
function peelTsAwaitCallee(node: SyntaxNode): SyntaxNode {
|
||||
function peelTsPrefixedCallee(node: SyntaxNode): SyntaxNode {
|
||||
let cur = node;
|
||||
while (cur.type === 'await_expression') {
|
||||
const inner = cur.namedChildren.find((c: SyntaxNode) => c.type !== 'comment');
|
||||
while (cur.type === 'await_expression' || cur.type === 'unary_expression') {
|
||||
const inner = cur.type === 'unary_expression'
|
||||
? getChildByField(cur, 'argument')
|
||||
: cur.namedChildren.find((c: SyntaxNode) => c.type !== 'comment');
|
||||
if (!inner) break;
|
||||
cur = inner;
|
||||
}
|
||||
@@ -593,6 +597,32 @@ function peelTsJsReceiver(node: SyntaxNode): SyntaxNode {
|
||||
return cur;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name a TS/JS call's callee writes when it is no member access, past the
|
||||
* wrappers that keep it the same callee — `(fn)()`, `fn!()`, `(fn as F)()`,
|
||||
* `await f<T>()`, the type arguments of a tagged template's tag
|
||||
* (`css<Props>\`…\``) — or null when it names nothing: a call (`connect(map)
|
||||
* (View)`, `create<T>()(…)`, `breakpoint('tablet')\`…\``), a function (an
|
||||
* IIFE), a subscript (`handlers[type]()`), `(0, fn)()`. Its text used to be
|
||||
* the reference's name, an IIFE's whole body among them, which no symbol is.
|
||||
* A dotted chain in parentheses keeps its text, `(a.b)()` → `a.b`, as the
|
||||
* parenthesized-callee normalization in extractCall reads it. `at` is where the
|
||||
* name starts, which is where the reference sits, as a plain `fn()` call's does:
|
||||
* scope checks read the name there (a parameter `fn` shadows a namesake).
|
||||
*/
|
||||
function tsJsCalleeName(func: SyntaxNode, source: string): { name: string; at: SyntaxNode } | null {
|
||||
let cur = peelTsJsReceiver(func);
|
||||
while (cur.type === 'instantiation_expression') {
|
||||
const inner = getChildByField(cur, 'function') ?? cur.namedChild(0);
|
||||
if (!inner) return null;
|
||||
cur = peelTsJsReceiver(inner);
|
||||
}
|
||||
if (cur.type === 'identifier' || cur.type === 'super' || cur.type === 'import') return { name: getNodeText(cur, source), at: cur };
|
||||
if (cur.type !== 'member_expression') return null;
|
||||
const chain = getNodeText(cur, source).replace(/\s+/g, '');
|
||||
return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(chain) ? { name: chain, at: cur } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a TS/JS receiver still collapses to the bare method name: `this` /
|
||||
* `super` (the resolver reads the owner off the enclosing class), a member
|
||||
@@ -5603,7 +5633,7 @@ export class TreeSitterExtractor {
|
||||
} else {
|
||||
let func = getChildByField(node, 'function') || node.namedChild(0);
|
||||
if (func && (this.language === 'typescript' || this.language === 'tsx' || this.language === 'arkts')) {
|
||||
const peeled = peelTsAwaitCallee(func);
|
||||
const peeled = peelTsPrefixedCallee(func);
|
||||
if (peeled !== func) refStart = peeled.startPosition;
|
||||
func = peeled;
|
||||
}
|
||||
@@ -5909,7 +5939,7 @@ export class TreeSitterExtractor {
|
||||
// wrong edge. The inner call is visited on its own either way.
|
||||
// Mirrored in the kernel (tsjs/extractors.rs, python.rs).
|
||||
let innerFn = getChildByField(receiver, 'function');
|
||||
if (innerFn && (this.language === 'typescript' || this.language === 'tsx')) innerFn = peelTsAwaitCallee(innerFn);
|
||||
if (innerFn && (this.language === 'typescript' || this.language === 'tsx')) innerFn = peelTsPrefixedCallee(innerFn);
|
||||
const innerCallee = innerFn ? getNodeText(innerFn, this.source).replace(/\s+/g, '') : '';
|
||||
if (!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(innerCallee)) return;
|
||||
calleeName = `${innerCallee}().${methodName}`;
|
||||
@@ -5992,6 +6022,15 @@ export class TreeSitterExtractor {
|
||||
} else {
|
||||
calleeName = getNodeText(func, this.source);
|
||||
}
|
||||
} else if (TS_JS_CHAIN_LANGUAGES.has(this.language)) {
|
||||
// A callee that names nothing — a call, a function, a subscript —
|
||||
// is no reference: a silent miss, never a name no symbol has. A
|
||||
// curried call's inner call is visited on its own. Mirrored in the
|
||||
// kernel's extract_call (tsjs/extractors.rs callee_name_of).
|
||||
const named = tsJsCalleeName(func, this.source);
|
||||
if (named === null) return;
|
||||
calleeName = named.name;
|
||||
refStart = named.at.startPosition;
|
||||
} else {
|
||||
calleeName = getNodeText(func, this.source);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user