aptu_coder_core/languages/
javascript.rs1pub const ELEMENT_QUERY: &str = r"
5(function_declaration) @function
6(class_declaration) @class
7(method_definition) @function
8(generator_function_declaration) @function
9
10";
11
12pub const CALL_QUERY: &str = r"
14(call_expression
15 function: (identifier) @call)
16(call_expression
17 function: (member_expression property: (property_identifier) @call))
18";
19
20pub const IMPORT_QUERY: &str = r#"
22(import_statement) @import_path
23(call_expression
24 function: (identifier) @_fn (#eq? @_fn "require")
25 arguments: (arguments (string) @import_path))
26"#;
27
28pub const DEFUSE_QUERY: &str = r"
30(variable_declarator name: (identifier) @write.declarator)
31(assignment_expression left: (identifier) @write.assign)
32(augmented_assignment_expression left: (identifier) @writeread.augmented)
33(update_expression argument: (identifier) @writeread.update)
34(identifier) @read.usage
35";
36
37use tree_sitter::Node;
44
45#[must_use]
47pub fn extract_function_name(node: &Node, source: &str, _lang: &str) -> Option<String> {
48 if node.kind() != "function_declaration" && node.kind() != "method_definition" {
49 return None;
50 }
51 node.child_by_field_name("name").and_then(|n| {
52 let end = n.end_byte();
53 if end <= source.len() {
54 Some(source[n.start_byte()..end].to_string())
55 } else {
56 None
57 }
58 })
59}
60
61#[must_use]
63pub fn find_receiver_type(node: &Node, source: &str) -> Option<String> {
64 if node.kind() != "method_definition" {
65 return None;
66 }
67
68 let mut current = *node;
70 while let Some(parent) = current.parent() {
71 if parent.kind() == "class_declaration" {
72 return parent.child_by_field_name("name").and_then(|n| {
74 let end = n.end_byte();
75 if end <= source.len() {
76 Some(source[n.start_byte()..end].to_string())
77 } else {
78 None
79 }
80 });
81 }
82 current = parent;
83 }
84
85 None
86}
87
88#[must_use]
90pub fn find_method_for_receiver(
91 node: &Node,
92 source: &str,
93 _depth: Option<usize>,
94) -> Option<String> {
95 if node.kind() != "method_definition" {
96 return None;
97 }
98
99 let mut current = *node;
101 let mut in_class = false;
102 while let Some(parent) = current.parent() {
103 if parent.kind() == "class_declaration" {
104 in_class = true;
105 break;
106 }
107 current = parent;
108 }
109
110 if !in_class {
111 return None;
112 }
113
114 node.child_by_field_name("name").and_then(|n| {
116 let end = n.end_byte();
117 if end <= source.len() {
118 Some(source[n.start_byte()..end].to_string())
119 } else {
120 None
121 }
122 })
123}
124
125#[must_use]
127pub fn extract_inheritance(node: &Node, source: &str) -> Vec<String> {
128 let mut inherits = Vec::new();
129 for i in 0..node.named_child_count() {
131 if let Some(child) = node.named_child(u32::try_from(i).unwrap_or(u32::MAX))
132 && child.kind() == "class_heritage"
133 {
134 for j in 0..child.named_child_count() {
136 if let Some(clause) = child.named_child(u32::try_from(j).unwrap_or(u32::MAX))
137 && clause.kind() == "extends_clause"
138 && let Some(value) = clause.child_by_field_name("value")
139 {
140 let text = &source[value.start_byte()..value.end_byte()];
141 inherits.push(format!("extends {text}"));
142 }
143 }
144 }
145 }
146 inherits
147}
148
149#[cfg(all(test, feature = "lang-javascript"))]
150mod tests {
151 use crate::DefUseKind;
152 use crate::parser::SemanticExtractor;
153 use tree_sitter::Parser;
154
155 fn parse_js(src: &str) -> tree_sitter::Tree {
156 let mut parser = Parser::new();
157 parser
158 .set_language(&tree_sitter_javascript::LANGUAGE.into())
159 .unwrap();
160 parser.parse(src, None).unwrap()
161 }
162
163 fn find_node_by_kind<'a>(
164 node: tree_sitter::Node<'a>,
165 kind: &str,
166 ) -> Option<tree_sitter::Node<'a>> {
167 if node.kind() == kind {
168 return Some(node);
169 }
170 for i in 0..node.child_count() {
171 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
172 if let Some(found) = find_node_by_kind(child, kind) {
173 return Some(found);
174 }
175 }
176 }
177 None
178 }
179
180 #[test]
181 fn test_extract_function_name() {
182 let src = "function foo() {}";
184 let tree = parse_js(src);
185 let root = tree.root_node();
186
187 let func_node =
189 find_node_by_kind(root, "function_declaration").expect("expected function_declaration");
190
191 let result = super::extract_function_name(&func_node, src, "javascript");
193
194 assert_eq!(result, Some("foo".to_string()));
196 }
197
198 #[test]
199 fn test_extract_method_name() {
200 let src = "class C { bar() {} }";
202 let tree = parse_js(src);
203 let root = tree.root_node();
204
205 let method_node =
207 find_node_by_kind(root, "method_definition").expect("expected method_definition");
208
209 let result = super::extract_function_name(&method_node, src, "javascript");
211
212 assert_eq!(result, Some("bar".to_string()));
214 }
215
216 #[test]
217 fn test_find_receiver_type() {
218 let src = "class MyClass { baz() {} }";
220 let tree = parse_js(src);
221 let root = tree.root_node();
222
223 let method_node =
225 find_node_by_kind(root, "method_definition").expect("expected method_definition");
226
227 let result = super::find_receiver_type(&method_node, src);
229
230 assert_eq!(result, Some("MyClass".to_string()));
232 }
233
234 #[test]
235 fn test_find_method_for_receiver() {
236 let src = "class C { qux() {} }";
238 let tree = parse_js(src);
239 let root = tree.root_node();
240
241 let method_node =
243 find_node_by_kind(root, "method_definition").expect("expected method_definition");
244
245 let result = super::find_method_for_receiver(&method_node, src, None);
247
248 assert_eq!(result, Some("qux".to_string()));
250 }
251
252 #[test]
253 fn test_function_declaration() {
254 let src = "function greet() { return 42; }";
255 let tree = parse_js(src);
256 let root = tree.root_node();
257 let func = find_node_by_kind(root, "function_declaration");
258 assert!(func.is_some(), "expected to find function_declaration");
259 }
260
261 #[test]
262 fn test_arrow_function() {
263 let src = "const add = (a, b) => a + b;";
264 let tree = parse_js(src);
265 let root = tree.root_node();
266 let arrow = find_node_by_kind(root, "arrow_function");
267 assert!(arrow.is_some(), "expected to find arrow_function");
268 }
269
270 #[test]
271 fn test_class_declaration() {
272 let src = "class Foo extends Bar { method() {} }";
273 let tree = parse_js(src);
274 let root = tree.root_node();
275 let class = find_node_by_kind(root, "class_declaration");
276 assert!(class.is_some(), "expected to find class_declaration");
277 }
278
279 #[test]
280 fn test_es_import() {
281 let src = "import {x} from 'module';";
282 let tree = parse_js(src);
283 let root = tree.root_node();
284 let import = find_node_by_kind(root, "import_statement");
285 assert!(import.is_some(), "expected to find import_statement");
286 }
287
288 #[test]
289 fn test_commonjs_require() {
290 let src = "const lib = require('lib');";
291 let tree = parse_js(src);
292 let root = tree.root_node();
293 let call = find_node_by_kind(root, "call_expression");
294 assert!(call.is_some(), "expected to find call_expression");
295 }
296
297 #[test]
298 fn test_defuse_query_write_site() {
299 let src = "let y = 10;\n";
301 let sites =
302 SemanticExtractor::extract_def_use_for_file(src, "javascript", "y", "test.js", None);
303 assert!(!sites.is_empty(), "defuse sites should not be empty");
304 let has_write = sites.iter().any(|s| matches!(s.kind, DefUseKind::Write));
305 assert!(has_write, "should contain a Write DefUseSite");
306 }
307}