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:
co-authored by
Colby McHenry
parent
bffd50e4f1
commit
bb1d3093eb
@@ -608,6 +608,13 @@ impl<'t> Walker<'t> {
|
||||
} else {
|
||||
callee_name = method_name.to_string();
|
||||
}
|
||||
} else if let Some(r) = receiver.filter(|r| r.kind() == "call") {
|
||||
// Call receiver — `d.setdefault(k, []).append(v)` (#1683):
|
||||
// `<inner>().<method>`, or nothing when the inner callee
|
||||
// is not a plain name / attribute chain. Mirrors
|
||||
// TreeSitterExtractor.extractCall.
|
||||
let Some(inner) = self.plain_inner_callee(r) else { return };
|
||||
callee_name = format!("{inner}().{method_name}");
|
||||
} else {
|
||||
callee_name = method_name.to_string();
|
||||
}
|
||||
@@ -626,6 +633,22 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The callee of a call receiver when it is a plain identifier or attribute
|
||||
/// chain (`make`, `d.setdefault`), whitespace stripped (#1683).
|
||||
fn plain_inner_callee(&self, call: Node<'t>) -> Option<String> {
|
||||
let inner = call.child_by_field_name("function")?;
|
||||
let text: String = self.text(inner).chars().filter(|c| !c.is_whitespace()).collect();
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let ok = text.split('.').all(|seg| {
|
||||
let mut chars = seg.chars();
|
||||
matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
|
||||
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
});
|
||||
if ok { Some(text) } else { None }
|
||||
}
|
||||
|
||||
/// extractDecoratorsFor — python decorators are PRECEDING SIBLINGS inside
|
||||
/// decorated_definition. Only bare-identifier decorators yield a target
|
||||
/// (python's `call` kind isn't `call_expression`, and `attribute` isn't in
|
||||
|
||||
@@ -1103,9 +1103,14 @@ impl<'t> Walker<'t> {
|
||||
} else {
|
||||
callee_name = method_name.to_string();
|
||||
}
|
||||
} else if let Some(r) = receiver.filter(|r| r.kind() == "call_expression") {
|
||||
// Call receiver — `make().run()` (#1683): keep the inner
|
||||
// callee as `<inner>().<method>`, or emit nothing when it
|
||||
// is not a plain name / member chain. Mirrors
|
||||
// TreeSitterExtractor.extractCall.
|
||||
let Some(inner) = self.plain_inner_callee(r) else { return };
|
||||
callee_name = format!("{inner}().{method_name}");
|
||||
} else {
|
||||
// (the call-receiver re-encode branches are other
|
||||
// languages'; TS/JS keeps the bare method name)
|
||||
callee_name = method_name.to_string();
|
||||
}
|
||||
}
|
||||
@@ -1128,6 +1133,22 @@ impl<'t> Walker<'t> {
|
||||
|
||||
// --- extractInstantiation -----------------------------------------------------------
|
||||
|
||||
/// The callee of a call-expression receiver when it is a plain identifier
|
||||
/// or member chain (`make`, `d.setdefault`), whitespace stripped (#1683).
|
||||
fn plain_inner_callee(&self, call: Node<'t>) -> Option<String> {
|
||||
let inner = call.child_by_field_name("function")?;
|
||||
let text: String = self.text(inner).chars().filter(|c| !c.is_whitespace()).collect();
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let ok = text.split('.').all(|seg| {
|
||||
let mut chars = seg.chars();
|
||||
matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_' || c == '$')
|
||||
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
|
||||
});
|
||||
if ok { Some(text) } else { None }
|
||||
}
|
||||
|
||||
pub(super) fn extract_instantiation(&mut self, node: Node<'t>) {
|
||||
if self.stack.is_empty() {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user