feat: Include receiver names in method call extraction and improve built-in filtering
Addresses method call resolution ambiguity where bare method names couldn't be distinguished from function calls. Modifies tree-sitter extraction to include receiver names (e.g., "console.log" instead of just "log") while skipping common instance references like self/this. Updates built-in filtering to be language-specific and adds Python built-in method detection based on receiver types and method names.
This commit is contained in:
@@ -1139,7 +1139,23 @@ export class TreeSitterExtractor {
|
|||||||
// Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
|
// Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
|
||||||
const property = getChildByField(func, 'property') || getChildByField(func, 'field') || func.namedChild(1);
|
const property = getChildByField(func, 'property') || getChildByField(func, 'field') || func.namedChild(1);
|
||||||
if (property) {
|
if (property) {
|
||||||
calleeName = getNodeText(property, this.source);
|
const methodName = getNodeText(property, this.source);
|
||||||
|
// Include receiver name for qualified resolution (e.g., console.print → "console.print")
|
||||||
|
// This helps the resolver distinguish method calls from bare function calls
|
||||||
|
// (e.g., Python's console.print() vs builtin print())
|
||||||
|
// Skip self/this/cls as they don't aid resolution
|
||||||
|
const receiver = getChildByField(func, 'object') || getChildByField(func, 'operand') || func.namedChild(0);
|
||||||
|
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super']);
|
||||||
|
if (receiver && receiver.type === 'identifier') {
|
||||||
|
const receiverName = getNodeText(receiver, this.source);
|
||||||
|
if (!SKIP_RECEIVERS.has(receiverName)) {
|
||||||
|
calleeName = `${receiverName}.${methodName}`;
|
||||||
|
} else {
|
||||||
|
calleeName = methodName;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
calleeName = methodName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
|
} else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
|
||||||
// Scoped call: Module::function()
|
// Scoped call: Module::function()
|
||||||
|
|||||||
+19
-6
@@ -562,35 +562,48 @@ export class ReferenceResolver {
|
|||||||
*/
|
*/
|
||||||
private isBuiltInOrExternal(ref: UnresolvedRef): boolean {
|
private isBuiltInOrExternal(ref: UnresolvedRef): boolean {
|
||||||
const name = ref.referenceName;
|
const name = ref.referenceName;
|
||||||
|
const isJsTs = ref.language === 'typescript' || ref.language === 'javascript'
|
||||||
|
|| ref.language === 'tsx' || ref.language === 'jsx';
|
||||||
|
|
||||||
// JavaScript/TypeScript built-ins
|
// JavaScript/TypeScript built-ins
|
||||||
if (JS_BUILT_INS.has(name)) {
|
if (isJsTs && JS_BUILT_INS.has(name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common library calls
|
// Common JS/TS library calls (console.log, Math.floor, JSON.parse)
|
||||||
if (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.')) {
|
if (isJsTs && (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// React hooks from React itself
|
// React hooks from React itself
|
||||||
if (REACT_HOOKS.has(name)) {
|
if (isJsTs && REACT_HOOKS.has(name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Python built-ins
|
// Python built-ins (bare calls only — dotted calls like console.print are method calls)
|
||||||
if (ref.language === 'python' && PYTHON_BUILT_INS.has(name)) {
|
if (ref.language === 'python' && PYTHON_BUILT_INS.has(name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Python built-in method calls (e.g., list.extend, dict.update, self.xxx)
|
// Python built-in method calls (e.g., list.extend, dict.update)
|
||||||
if (ref.language === 'python') {
|
if (ref.language === 'python') {
|
||||||
const dotIdx = name.indexOf('.');
|
const dotIdx = name.indexOf('.');
|
||||||
if (dotIdx > 0) {
|
if (dotIdx > 0) {
|
||||||
const receiver = name.substring(0, dotIdx);
|
const receiver = name.substring(0, dotIdx);
|
||||||
|
const method = name.substring(dotIdx + 1);
|
||||||
|
// Filter calls on built-in types (list.append, dict.update, etc.)
|
||||||
if (PYTHON_BUILT_IN_TYPES.has(receiver)) {
|
if (PYTHON_BUILT_IN_TYPES.has(receiver)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Filter built-in methods on non-class receivers
|
||||||
|
// (e.g., items.append where items is a local list variable)
|
||||||
|
// But allow if the capitalized receiver matches a known codebase class
|
||||||
|
if (PYTHON_BUILT_IN_METHODS.has(method)) {
|
||||||
|
const capitalized = receiver.charAt(0).toUpperCase() + receiver.slice(1);
|
||||||
|
if (!this.knownNames?.has(capitalized)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (PYTHON_BUILT_IN_METHODS.has(name)) {
|
if (PYTHON_BUILT_IN_METHODS.has(name)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user