diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index 05ac094..059d147 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -818,6 +818,47 @@ public protocol Repository { expect(protocolNode).toBeDefined(); 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', () => { diff --git a/src/extraction/tree-sitter.ts b/src/extraction/tree-sitter.ts index a4197ab..b8edca5 100644 --- a/src/extraction/tree-sitter.ts +++ b/src/extraction/tree-sitter.ts @@ -583,6 +583,9 @@ export class TreeSitterExtractor { }); if (!structNode) return; + // Extract inheritance (e.g. Swift: struct HTTPMethod: RawRepresentable) + this.extractInheritance(node, structNode.id); + // Push to stack for field extraction this.nodeStack.push(structNode.id); 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) if (child.type === 'field_declaration_list') { this.extractInheritance(child, classId);