Kaynağa Gözat

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.
Colby McHenry 2 ay önce
ebeveyn
işleme
e12bd7ce91
2 değiştirilmiş dosya ile 36 ekleme ve 7 silme
  1. 17 1
      src/extraction/tree-sitter.ts
  2. 19 6
      src/resolution/index.ts

+ 17 - 1
src/extraction/tree-sitter.ts

@@ -1139,7 +1139,23 @@ export class TreeSitterExtractor {
           // Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
           const property = getChildByField(func, 'property') || getChildByField(func, 'field') || func.namedChild(1);
           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') {
           // Scoped call: Module::function()

+ 19 - 6
src/resolution/index.ts

@@ -562,35 +562,48 @@ export class ReferenceResolver {
    */
   private isBuiltInOrExternal(ref: UnresolvedRef): boolean {
     const name = ref.referenceName;
+    const isJsTs = ref.language === 'typescript' || ref.language === 'javascript'
+      || ref.language === 'tsx' || ref.language === 'jsx';
 
     // JavaScript/TypeScript built-ins
-    if (JS_BUILT_INS.has(name)) {
+    if (isJsTs && JS_BUILT_INS.has(name)) {
       return true;
     }
 
-    // Common library calls
-    if (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.')) {
+    // Common JS/TS library calls (console.log, Math.floor, JSON.parse)
+    if (isJsTs && (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.'))) {
       return true;
     }
 
     // React hooks from React itself
-    if (REACT_HOOKS.has(name)) {
+    if (isJsTs && REACT_HOOKS.has(name)) {
       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)) {
       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') {
       const dotIdx = name.indexOf('.');
       if (dotIdx > 0) {
         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)) {
           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)) {
         return true;