Sfoglia il codice sorgente

feat: Add Swift inheritance extraction for class, struct, enum, and protocol relationships

Addresses Swift's inheritance_specifier syntax where type relationships are specified after colons (e.g. `class UploadRequest: DataRequest, Sendable`). Extracts user_type > type_identifier children from inheritance_specifier nodes as 'extends' references to properly model Swift's inheritance, protocol conformance, and struct conformance patterns in the code graph.
Colby McHenry 2 mesi fa
parent
commit
5046c760cb
2 ha cambiato i file con 61 aggiunte e 0 eliminazioni
  1. 41 0
      __tests__/extraction.test.ts
  2. 20 0
      src/extraction/tree-sitter.ts

+ 41 - 0
__tests__/extraction.test.ts

@@ -818,6 +818,47 @@ public protocol Repository {
     expect(protocolNode).toBeDefined();
     expect(protocolNode).toBeDefined();
     expect(protocolNode?.name).toBe('Repository');
     expect(protocolNode?.name).toBe('Repository');
   });
   });
+
+  it('should extract class inheritance and protocol conformance', () => {
+    const code = `
+class DataRequest: Request {
+    func validate() {}
+}
+
+class UploadRequest: DataRequest, Sendable {
+    func upload() {}
+}
+
+enum AFError: Error {
+    case invalidURL
+}
+
+struct HTTPMethod: RawRepresentable {
+    let rawValue: String
+}
+
+protocol UploadConvertible: URLRequestConvertible {
+    func asURLRequest() throws -> URLRequest
+}
+`;
+    const result = extractFromSource('Inheritance.swift', code);
+
+    const extendsRefs = result.unresolvedReferences.filter(
+      (r) => r.referenceKind === 'extends'
+    );
+
+    // DataRequest extends Request
+    expect(extendsRefs.find((r) => r.referenceName === 'Request')).toBeDefined();
+    // UploadRequest extends DataRequest and Sendable
+    expect(extendsRefs.find((r) => r.referenceName === 'DataRequest')).toBeDefined();
+    expect(extendsRefs.find((r) => r.referenceName === 'Sendable')).toBeDefined();
+    // AFError extends Error
+    expect(extendsRefs.find((r) => r.referenceName === 'Error')).toBeDefined();
+    // HTTPMethod extends RawRepresentable
+    expect(extendsRefs.find((r) => r.referenceName === 'RawRepresentable')).toBeDefined();
+    // UploadConvertible extends URLRequestConvertible
+    expect(extendsRefs.find((r) => r.referenceName === 'URLRequestConvertible')).toBeDefined();
+  });
 });
 });
 
 
 describe('Kotlin Extraction', () => {
 describe('Kotlin Extraction', () => {

+ 20 - 0
src/extraction/tree-sitter.ts

@@ -583,6 +583,9 @@ export class TreeSitterExtractor {
     });
     });
     if (!structNode) return;
     if (!structNode) return;
 
 
+    // Extract inheritance (e.g. Swift: struct HTTPMethod: RawRepresentable)
+    this.extractInheritance(node, structNode.id);
+
     // Push to stack for field extraction
     // Push to stack for field extraction
     this.nodeStack.push(structNode.id);
     this.nodeStack.push(structNode.id);
     const body = getChildByField(node, this.extractor.bodyField) || node;
     const body = getChildByField(node, this.extractor.bodyField) || node;
@@ -1309,6 +1312,23 @@ export class TreeSitterExtractor {
         }
         }
       }
       }
 
 
+      // Swift: inheritance_specifier > user_type > type_identifier
+      // Used for class inheritance, protocol conformance, and protocol inheritance
+      if (child.type === 'inheritance_specifier') {
+        const userType = child.namedChildren.find((c: SyntaxNode) => c.type === 'user_type');
+        const typeId = userType?.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
+        if (typeId) {
+          const name = getNodeText(typeId, this.source);
+          this.unresolvedReferences.push({
+            fromNodeId: classId,
+            referenceName: name,
+            referenceKind: 'extends',
+            line: typeId.startPosition.row + 1,
+            column: typeId.startPosition.column,
+          });
+        }
+      }
+
       // Recurse into container nodes (e.g. field_declaration_list in Go structs)
       // Recurse into container nodes (e.g. field_declaration_list in Go structs)
       if (child.type === 'field_declaration_list') {
       if (child.type === 'field_declaration_list') {
         this.extractInheritance(child, classId);
         this.extractInheritance(child, classId);