Classify Go interfaces, structs, and type aliases correctly

Use type_spec and type_alias tree-sitter nodes instead of type_declaration,
then inspect the child node kind to distinguish interface_type (→ Interface)
from struct_type (→ Class) and everything else (→ Type).

Adds tests for standalone and grouped type declarations.
This commit is contained in:
Martin Vogel
2026-02-25 10:06:51 +01:00
parent 555f874622
commit 809489c125
3 changed files with 134 additions and 3 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ func init() {
Language: Go,
FileExtensions: []string{".go"},
FunctionNodeTypes: []string{"function_declaration", "method_declaration"},
ClassNodeTypes: []string{"type_declaration"},
ClassNodeTypes: []string{"type_spec", "type_alias"},
ModuleNodeTypes: []string{"source_file"},
CallNodeTypes: []string{"call_expression"},
ImportNodeTypes: []string{"import_declaration"},
+14 -2
View File
@@ -494,6 +494,18 @@ func (p *Pipeline) extractClass(
// Determine specific label
label := classLabelForKind(node.Kind())
// Go type_spec: determine actual type from child (interface vs struct)
if node.Kind() == "type_spec" {
if typeNode := node.ChildByFieldName("type"); typeNode != nil {
switch typeNode.Kind() {
case "interface_type":
label = "Interface"
case "struct_type":
label = "Class"
}
}
}
startLine := int(node.StartPosition().Row) + 1
endLine := int(node.EndPosition().Row) + 1
@@ -838,11 +850,11 @@ func isExported(name string, language lang.Language) bool {
func classLabelForKind(kind string) string {
switch kind {
case "interface_declaration", "trait_item", "trait_definition":
case "interface_declaration", "trait_item", "trait_definition", "trait_declaration":
return "Interface"
case "enum_declaration", "enum_item", "enum_specifier":
return "Enum"
case "type_declaration", "type_alias_declaration", "type_item":
case "type_declaration", "type_alias_declaration", "type_item", "type_spec", "type_alias":
return "Type"
case "union_specifier", "union_item":
return "Union"
+119
View File
@@ -369,6 +369,125 @@ def run():
}
}
// TestGoTypeClassification verifies that Go interfaces, structs, and type aliases
// are correctly classified as Interface, Class, and Type respectively.
func TestGoTypeClassification(t *testing.T) {
dir, err := os.MkdirTemp("", "cgm-go-types-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
writeFile(t, filepath.Join(dir, "types.go"), `package types
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Config struct {
Host string
Port int
}
type ID = string
`)
s, err := store.OpenMemory()
if err != nil {
t.Fatal(err)
}
defer s.Close()
p := New(s, dir)
if err := p.Run(); err != nil {
t.Fatalf("Pipeline.Run: %v", err)
}
// Check Interface nodes (Reader, Writer)
interfaces, _ := s.FindNodesByLabel(p.ProjectName, "Interface")
if len(interfaces) != 2 {
t.Errorf("expected 2 Interface nodes, got %d", len(interfaces))
for _, n := range interfaces {
t.Logf(" Interface: %s", n.Name)
}
}
// Check Class nodes (Config struct)
classes, _ := s.FindNodesByLabel(p.ProjectName, "Class")
if len(classes) != 1 {
t.Errorf("expected 1 Class node (struct), got %d", len(classes))
for _, n := range classes {
t.Logf(" Class: %s", n.Name)
}
} else if classes[0].Name != "Config" {
t.Errorf("expected Class name 'Config', got %q", classes[0].Name)
}
// Check Type nodes (ID type alias)
types, _ := s.FindNodesByLabel(p.ProjectName, "Type")
if len(types) != 1 {
t.Errorf("expected 1 Type node (alias), got %d", len(types))
for _, n := range types {
t.Logf(" Type: %s", n.Name)
}
} else if types[0].Name != "ID" {
t.Errorf("expected Type name 'ID', got %q", types[0].Name)
}
}
// TestGoGroupedTypeDeclaration verifies that grouped type declarations
// (type ( ... )) also work correctly.
func TestGoGroupedTypeDeclaration(t *testing.T) {
dir, err := os.MkdirTemp("", "cgm-go-grouped-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
writeFile(t, filepath.Join(dir, "models.go"), `package models
type (
Request struct {
URL string
}
Response struct {
Status int
}
Handler interface {
Handle(req Request) Response
}
)
`)
s, err := store.OpenMemory()
if err != nil {
t.Fatal(err)
}
defer s.Close()
p := New(s, dir)
if err := p.Run(); err != nil {
t.Fatalf("Pipeline.Run: %v", err)
}
// Should find 2 structs (Class) and 1 interface
classes, _ := s.FindNodesByLabel(p.ProjectName, "Class")
if len(classes) != 2 {
t.Errorf("expected 2 Class nodes (structs), got %d", len(classes))
}
interfaces, _ := s.FindNodesByLabel(p.ProjectName, "Interface")
if len(interfaces) != 1 {
t.Errorf("expected 1 Interface node, got %d", len(interfaces))
}
}
// TestFunctionRegistry tests the registry in isolation.
func TestFunctionRegistry(t *testing.T) {
r := NewFunctionRegistry()