1use super::{add_contains_edge, add_node_if_missing, make_file_node, run_query_named};
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 TsCppExtractor;
9impl TsCppExtractor {
10 pub fn extract(source: &[u8], path: &Path) -> Result<ExtractionFragment> {
11 let (file_id, _, file_node) = make_file_node(path);
12 let mut fragment = ExtractionFragment {
13 nodes: vec![file_node],
14 edges: vec![],
15 };
16 let lang = tree_sitter_cpp::LANGUAGE.into();
17
18 let class_query = r#"
20 (class_specifier
21 name: (type_identifier) @class.name
22 )
23 "#;
24 if let Ok(mut captures) = run_query_named(source, &lang, class_query) {
25 let names = captures.remove("class.name").unwrap_or_default();
26 for name in &names {
27 let class_id = make_id(&[&file_id, name]);
28 add_node_if_missing(
29 &mut fragment,
30 Node {
31 id: class_id.clone(),
32 label: name.to_string(),
33 file_type: "class".to_string(),
34 source_file: path.to_string_lossy().to_string(),
35 source_location: None,
36 community: None,
37 rationale: None,
38 docstring: None,
39 metadata: HashMap::new(),
40 },
41 );
42 add_contains_edge(&mut fragment, &file_id, class_id, path);
43 }
44 }
45
46 let struct_query = r#"
48 (struct_specifier
49 name: (type_identifier) @struct.name
50 )
51 "#;
52 if let Ok(mut captures) = run_query_named(source, &lang, struct_query) {
53 let names = captures.remove("struct.name").unwrap_or_default();
54 for name in &names {
55 let id = make_id(&[&file_id, name]);
56 add_node_if_missing(
57 &mut fragment,
58 Node {
59 id: id.clone(),
60 label: name.to_string(),
61 file_type: "struct".to_string(),
62 source_file: path.to_string_lossy().to_string(),
63 source_location: None,
64 community: None,
65 rationale: None,
66 docstring: None,
67 metadata: HashMap::new(),
68 },
69 );
70 add_contains_edge(&mut fragment, &file_id, id, path);
71 }
72 }
73
74 let _inherits_query = r#"
76 (class_specifier
77 name: (type_identifier) @child.name
78 (base_class_clause
79 (type_identifier) @base.name
80 )
81 )
82 "#;
83 Self::extract_inheritance(source, path, &file_id, &lang, &mut fragment);
87
88 let fn_top_query = r#"
90 (function_definition
91 declarator: (function_declarator
92 declarator: (identifier) @fn.name
93 )
94 )
95 "#;
96 if let Ok(mut captures) = run_query_named(source, &lang, fn_top_query) {
97 for name in captures.remove("fn.name").unwrap_or_default() {
98 let fn_id = make_id(&[&file_id, &name, "()"]);
99 add_node_if_missing(
100 &mut fragment,
101 Node {
102 id: fn_id.clone(),
103 label: format!("{}()", name),
104 file_type: "function".to_string(),
105 source_file: path.to_string_lossy().to_string(),
106 source_location: None,
107 community: None,
108 rationale: None,
109 docstring: None,
110 metadata: HashMap::new(),
111 },
112 );
113 add_contains_edge(&mut fragment, &file_id, fn_id, path);
114 }
115 }
116
117 let fn_member_query = r#"
119 [
120 (function_definition
121 declarator: (function_declarator
122 declarator: (field_identifier) @method.name
123 )
124 )
125 (field_declaration
126 declarator: (function_declarator
127 declarator: (field_identifier) @method.name
128 )
129 )
130 ]
131 "#;
132 if let Ok(mut captures) = run_query_named(source, &lang, fn_member_query) {
133 for name in captures.remove("method.name").unwrap_or_default() {
134 let fn_id = make_id(&[&file_id, &name, "()"]);
135 add_node_if_missing(
136 &mut fragment,
137 Node {
138 id: fn_id.clone(),
139 label: format!("{}()", name),
140 file_type: "method".to_string(),
141 source_file: path.to_string_lossy().to_string(),
142 source_location: None,
143 community: None,
144 rationale: None,
145 docstring: None,
146 metadata: HashMap::new(),
147 },
148 );
149 add_contains_edge(&mut fragment, &file_id, fn_id, path);
150 }
151 }
152
153 let include_query = r#"
155 [
156 (preproc_include path: (string_literal) @include.path)
157 (preproc_include path: (system_lib_string) @include.path)
158 ]
159 "#;
160 if let Ok(mut captures) = run_query_named(source, &lang, include_query) {
161 for p in captures.remove("include.path").unwrap_or_default() {
162 let inc = p
163 .trim()
164 .trim_matches('"')
165 .trim_matches('<')
166 .trim_matches('>');
167 let inc_id = make_id(&[&file_id, inc]);
168 add_node_if_missing(
169 &mut fragment,
170 Node {
171 id: inc_id.clone(),
172 label: inc.to_string(),
173 file_type: "code".to_string(),
174 source_file: path.to_string_lossy().to_string(),
175 source_location: None,
176 community: None,
177 rationale: None,
178 docstring: None,
179 metadata: HashMap::new(),
180 },
181 );
182 fragment.edges.push(Edge {
183 source: file_id.clone(),
184 target: inc_id,
185 relation: "imports".to_string(),
186 confidence: "EXTRACTED".to_string(),
187 source_file: Some(path.to_string_lossy().to_string()),
188 weight: 1.0,
189 context: Some("import".to_string()),
190 });
191 }
192 }
193
194 Ok(fragment)
195 }
196
197 fn extract_inheritance(
198 source: &[u8],
199 path: &Path,
200 file_id: &str,
201 lang: &tree_sitter::Language,
202 fragment: &mut ExtractionFragment,
203 ) {
204 use tree_sitter::{Node as TsNode, Parser};
205
206 let mut parser = Parser::new();
207 if parser.set_language(lang).is_err() {
208 return;
209 }
210 let tree = match parser.parse(source, None) {
211 Some(t) => t,
212 None => return,
213 };
214
215 fn text(source: &[u8], node: &TsNode<'_>) -> String {
216 std::str::from_utf8(&source[node.start_byte()..node.end_byte()])
217 .unwrap_or("")
218 .trim()
219 .to_string()
220 }
221
222 fn walk(
223 node: TsNode<'_>,
224 source: &[u8],
225 path: &Path,
226 file_id: &str,
227 fragment: &mut ExtractionFragment,
228 ) {
229 let kind = node.kind();
230 if kind == "class_specifier" || kind == "struct_specifier" {
231 let name_node = node.child_by_field_name("name");
233 if let Some(name_node) = name_node {
234 let class_name = text(source, &name_node);
235 let class_id = make_id(&[file_id, &class_name]);
236
237 for i in 0..node.child_count() {
239 if let Some(child) = node.child(i) {
240 if child.kind() == "base_class_clause" {
241 for j in 0..child.child_count() {
243 if let Some(base) = child.child(j) {
244 if base.kind() == "type_identifier" {
245 let base_name = text(source, &base);
246 let base_id = make_id(&[&base_name]);
247 add_node_if_missing(
248 fragment,
249 Node {
250 id: base_id.clone(),
251 label: base_name,
252 file_type: "code".to_string(),
253 source_file: path.to_string_lossy().to_string(),
254 source_location: None,
255 community: None,
256 rationale: None,
257 docstring: None,
258 metadata: HashMap::new(),
259 },
260 );
261 let already = fragment.edges.iter().any(|e| {
262 e.relation == "inherits"
263 && e.source == class_id
264 && e.target == base_id
265 });
266 if !already {
267 fragment.edges.push(Edge {
268 source: class_id.clone(),
269 target: base_id,
270 relation: "inherits".to_string(),
271 confidence: "EXTRACTED".to_string(),
272 source_file: Some(
273 path.to_string_lossy().to_string(),
274 ),
275 weight: 1.0,
276 context: None,
277 });
278 }
279 }
280 }
281 }
282 }
283 walk(child, source, path, file_id, fragment);
285 }
286 }
287 }
288 return;
289 }
290
291 for i in 0..node.child_count() {
292 if let Some(child) = node.child(i) {
293 walk(child, source, path, file_id, fragment);
294 }
295 }
296 }
297
298 walk(tree.root_node(), source, path, file_id, fragment);
299 }
300}
301
302impl LanguageExtractor for TsCppExtractor {
303 fn file_extensions(&self) -> Vec<&'static str> {
304 vec!["cpp", "cxx", "hpp", "hxx", "cc"]
305 }
306 fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
307 Self::extract(source, path)
308 }
309 fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
310 vec![]
311 }
312 fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
313}