fix(extraction): never fabricate an edge from a call-result receiver (#1748)

A member call whose receiver is itself a call — `d.setdefault(k, []).append(v)`,
`make().run()` — used to drop the receiver at extraction time, degrade to the
bare method name, and exact-match any top-level project symbol of that name
(Python and JavaScript/TypeScript). Keep the inner callee encoded as
`<inner>().<method>` in the TS extractor and native kernel; the name-matcher
refuses to guess for that shape (store-accessor exception only). Based on
#1692, rebased onto main after #1746. Fixes #1683.

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 00:23:08 -05:00
committed by GitHub
co-authored by Colby McHenry
parent bffd50e4f1
commit bb1d3093eb
10 changed files with 226 additions and 3 deletions
+26
View File
@@ -4573,6 +4573,32 @@ export class TreeSitterExtractor {
// scope keywords: such calls previously emitted a bare method
// name, which either failed to resolve or resolved ambiguously.
calleeName = `${getNodeText(receiver, this.source)}.${methodName}`;
} else if (
(this.language === 'typescript' ||
this.language === 'javascript' ||
this.language === 'tsx' ||
this.language === 'jsx' ||
this.language === 'python') &&
receiver &&
(receiver.type === 'call_expression' || receiver.type === 'call')
) {
// Receiver that is itself a call — `d.setdefault(k, []).append(v)`,
// `make().run()`, `res.json().data` (#1683). The bare method name
// this used to emit exact-matched any top-level project symbol of
// that name and fabricated a call edge from an unrelated function
// (`append`, `get`, `run`…). Keep the inner callee, encoded as
// `<inner>().<method>` like the Java/Kotlin/C++ chains: the
// marker never appears in an ordinary ref, so nothing name-matches
// it, and a chain resolver can later infer the receiver's type
// from what the inner call returns. An inner callee that is not a
// plain name or member chain (`(await x)()`, `arr[0]()`) has no
// static receiver at all — emit nothing: a silent miss, never a
// wrong edge. The inner call is visited on its own either way.
// Mirrored in the kernel (tsjs/extractors.rs, python.rs).
const innerFn = getChildByField(receiver, 'function');
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}`;
} else if (
this.language === 'go' &&
receiver &&