From 1244c62193a03b2d815f1985a9c42971759bdfc8 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Mon, 6 Apr 2026 18:47:12 -0500 Subject: [PATCH] feat: Add Go struct and interface embedding extraction for inheritance relationships Addresses Go's embedding mechanism where structs can embed other types without field names (e.g. `type DB struct { *Head; Queryable }`) and interfaces can embed other interfaces via constraint_elem nodes. Extracts these embedded types as 'extends' relationships to properly model Go's composition-based inheritance patterns in the code graph. --- src/extraction/tree-sitter.ts | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/extraction/tree-sitter.ts b/src/extraction/tree-sitter.ts index a883e9c..4a08d7c 100644 --- a/src/extraction/tree-sitter.ts +++ b/src/extraction/tree-sitter.ts @@ -878,6 +878,8 @@ export class TreeSitterExtractor { this.nodeStack.push(structNode.id); const typeChild = getChildByField(node, 'type'); if (typeChild) { + // Extract struct embedding (e.g. Go: `type DB struct { *Head; Queryable }`) + this.extractInheritance(typeChild, structNode.id); const body = getChildByField(typeChild, this.extractor.bodyField) || typeChild; for (let i = 0; i < body.namedChildCount; i++) { const child = body.namedChild(i); @@ -1225,6 +1227,46 @@ export class TreeSitterExtractor { } } } + + // Go interface embedding: `type Querier interface { LabelQuerier; ... }` + // constraint_elem wraps the embedded interface type identifier + if (child.type === 'constraint_elem') { + const typeId = child.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, + }); + } + } + + // Go struct embedding: field_declaration without field_identifier + // e.g. `type DB struct { *Head; Queryable }` — no field name means embedded type + if (child.type === 'field_declaration') { + const hasFieldIdentifier = child.namedChildren.some((c: SyntaxNode) => c.type === 'field_identifier'); + if (!hasFieldIdentifier) { + const typeId = child.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); + } } }