Skip to main content

codesynapse_core/ts_extract/
go.rs

1use super::{add_contains_edge, add_node_if_missing, make_file_node, run_query_matches_ranged};
2use crate::error::Result;
3use crate::extract::{make_id, ImportNode, LanguageExtractor};
4use crate::types::{Edge, ExtractionFragment, Node};
5use std::collections::HashMap;
6use std::path::Path;
7
8pub struct TsGoExtractor;
9
10impl TsGoExtractor {
11    pub fn extract(source: &[u8], path: &Path) -> Result<ExtractionFragment> {
12        let (file_id, _, file_node) = make_file_node(path);
13        let mut fragment = ExtractionFragment {
14            nodes: vec![file_node],
15            edges: vec![],
16        };
17
18        let lang = tree_sitter_go::LANGUAGE.into();
19
20        // Struct definitions with source_location
21        let struct_query = r#"
22            (type_declaration
23                (type_spec
24                    name: (type_identifier) @struct.name
25                    type: (struct_type)
26                )
27            ) @struct.node
28        "#;
29        if let Ok(matches) = run_query_matches_ranged(source, &lang, struct_query) {
30            for (texts, ranges) in &matches {
31                let name = match texts.get("struct.name") {
32                    Some(n) => n.clone(),
33                    None => continue,
34                };
35                let source_location = ranges
36                    .get("struct.node")
37                    .map(|(s, e)| format!("{}:{}", s, e));
38                let struct_id = make_id(&[&file_id, &name]);
39                fragment.nodes.push(Node {
40                    id: struct_id.clone(),
41                    label: name.clone(),
42                    file_type: "struct".to_string(),
43                    source_file: path.to_string_lossy().to_string(),
44                    source_location,
45                    community: None,
46                    rationale: None,
47                    docstring: None,
48                    metadata: {
49                        let mut m = HashMap::new();
50                        m.insert("kind".to_string(), "struct".to_string());
51                        m
52                    },
53                });
54                add_contains_edge(&mut fragment, &file_id, struct_id, path);
55            }
56        }
57
58        // Top-level functions
59        let fn_query = r#"
60            (function_declaration
61                name: (identifier) @fn.name
62            ) @fn.node
63        "#;
64        if let Ok(matches) = run_query_matches_ranged(source, &lang, fn_query) {
65            for (texts, ranges) in &matches {
66                let name = match texts.get("fn.name") {
67                    Some(n) => n.clone(),
68                    None => continue,
69                };
70                let source_location = ranges.get("fn.node").map(|(s, e)| format!("{}:{}", s, e));
71                let fn_id = make_id(&[&file_id, &name]);
72                let label = format!("{}()", name);
73                fragment.nodes.push(Node {
74                    id: fn_id.clone(),
75                    label,
76                    file_type: "function".to_string(),
77                    source_file: path.to_string_lossy().to_string(),
78                    source_location,
79                    community: None,
80                    rationale: None,
81                    docstring: None,
82                    metadata: {
83                        let mut m = HashMap::new();
84                        m.insert("kind".to_string(), "function".to_string());
85                        m
86                    },
87                });
88                add_contains_edge(&mut fragment, &file_id, fn_id, path);
89            }
90        }
91
92        // Pointer receiver methods: func (e *Engine) ServeHTTP(...)
93        let ptr_recv_query = r#"
94            (method_declaration
95                receiver: (parameter_list
96                    (parameter_declaration
97                        type: (pointer_type (type_identifier) @recv.type)
98                    )
99                )
100                name: (field_identifier) @fn.name
101            ) @fn.node
102        "#;
103        Self::extract_methods(source, &lang, path, &file_id, &mut fragment, ptr_recv_query);
104
105        // Value receiver methods: func (e Engine) ServeHTTP(...)
106        let val_recv_query = r#"
107            (method_declaration
108                receiver: (parameter_list
109                    (parameter_declaration
110                        type: (type_identifier) @recv.type
111                    )
112                )
113                name: (field_identifier) @fn.name
114            ) @fn.node
115        "#;
116        Self::extract_methods(source, &lang, path, &file_id, &mut fragment, val_recv_query);
117
118        Ok(fragment)
119    }
120
121    fn extract_methods(
122        source: &[u8],
123        lang: &tree_sitter::Language,
124        path: &Path,
125        file_id: &String,
126        fragment: &mut ExtractionFragment,
127        query_str: &str,
128    ) {
129        let Ok(matches) = run_query_matches_ranged(source, lang, query_str) else {
130            return;
131        };
132
133        // Build set of struct IDs already in fragment for contains edge guard
134        let existing_struct_ids: std::collections::HashSet<String> =
135            fragment.nodes.iter().map(|n| n.id.clone()).collect();
136
137        for (texts, ranges) in &matches {
138            let fn_name = match texts.get("fn.name") {
139                Some(n) => n.clone(),
140                None => continue,
141            };
142            let recv_type = match texts.get("recv.type") {
143                Some(t) => t.clone(),
144                None => continue,
145            };
146            let source_location = ranges.get("fn.node").map(|(s, e)| format!("{}:{}", s, e));
147            let method_id = make_id(&[file_id, &recv_type, &fn_name]);
148            let label = format!("{}()", fn_name);
149
150            let method_node = Node {
151                id: method_id.clone(),
152                label,
153                file_type: "method".to_string(),
154                source_file: path.to_string_lossy().to_string(),
155                source_location,
156                community: None,
157                rationale: None,
158                docstring: None,
159                metadata: {
160                    let mut m = HashMap::new();
161                    m.insert("kind".to_string(), "method".to_string());
162                    m
163                },
164            };
165            add_node_if_missing(fragment, method_node);
166
167            // file → method contains edge
168            add_contains_edge(fragment, file_id, method_id.clone(), path);
169
170            // struct → method contains edge if struct exists in fragment
171            let struct_id = make_id(&[file_id, &recv_type]);
172            if existing_struct_ids.contains(&struct_id) {
173                fragment.edges.push(Edge {
174                    source: struct_id,
175                    target: method_id,
176                    relation: "contains".to_string(),
177                    confidence: "EXTRACTED".to_string(),
178                    source_file: Some(path.to_string_lossy().to_string()),
179                    weight: 1.0,
180                    context: None,
181                });
182            }
183        }
184    }
185}
186
187/// Tree-sitter based Go extractor
188impl LanguageExtractor for TsGoExtractor {
189    fn file_extensions(&self) -> Vec<&'static str> {
190        vec!["go"]
191    }
192    fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
193        Self::extract(source, path)
194    }
195    fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
196        vec![]
197    }
198    fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
199}