Skip to main content

code_analyze_core/languages/
javascript.rs

1// SPDX-FileCopyrightText: 2026 code-analyze-mcp contributors
2// SPDX-License-Identifier: Apache-2.0
3/// Tree-sitter query for extracting JavaScript elements (functions, classes, and related).
4pub 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
12/// Tree-sitter query for extracting function calls.
13pub 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
20/// Tree-sitter query for extracting JavaScript imports (ESM and CommonJS).
21pub 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
28// JavaScript intentionally has no REFERENCE_QUERY. JavaScript's dynamic typing
29// makes static type reference extraction low-value: most "type" references in JS
30// are just identifiers that appear in many non-type contexts, producing excessive
31// false positives with no meaningful signal. The `reference_query` field is set
32// to `None` for the JavaScript handler in `mod.rs`.
33
34use tree_sitter::Node;
35
36/// Extract inheritance information from a JavaScript class node.
37#[must_use]
38pub fn extract_inheritance(node: &Node, source: &str) -> Vec<String> {
39    let mut inherits = Vec::new();
40    // Walk children to find class_heritage node
41    for i in 0..node.named_child_count() {
42        if let Some(child) = node.named_child(u32::try_from(i).unwrap_or(u32::MAX))
43            && child.kind() == "class_heritage"
44        {
45            // Walk class_heritage children for extends_clause
46            for j in 0..child.named_child_count() {
47                if let Some(clause) = child.named_child(u32::try_from(j).unwrap_or(u32::MAX))
48                    && clause.kind() == "extends_clause"
49                    && let Some(value) = clause.child_by_field_name("value")
50                {
51                    let text = &source[value.start_byte()..value.end_byte()];
52                    inherits.push(format!("extends {text}"));
53                }
54            }
55        }
56    }
57    inherits
58}
59
60#[cfg(all(test, feature = "lang-javascript"))]
61mod tests {
62    use tree_sitter::Parser;
63
64    fn parse_js(src: &str) -> tree_sitter::Tree {
65        let mut parser = Parser::new();
66        parser
67            .set_language(&tree_sitter_javascript::LANGUAGE.into())
68            .unwrap();
69        parser.parse(src, None).unwrap()
70    }
71
72    fn find_node_by_kind<'a>(
73        node: tree_sitter::Node<'a>,
74        kind: &str,
75    ) -> Option<tree_sitter::Node<'a>> {
76        if node.kind() == kind {
77            return Some(node);
78        }
79        for i in 0..node.child_count() {
80            if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
81                if let Some(found) = find_node_by_kind(child, kind) {
82                    return Some(found);
83                }
84            }
85        }
86        None
87    }
88
89    #[test]
90    fn test_function_declaration() {
91        let src = "function greet() { return 42; }";
92        let tree = parse_js(src);
93        let root = tree.root_node();
94        let func = find_node_by_kind(root, "function_declaration");
95        assert!(func.is_some(), "expected to find function_declaration");
96    }
97
98    #[test]
99    fn test_arrow_function() {
100        let src = "const add = (a, b) => a + b;";
101        let tree = parse_js(src);
102        let root = tree.root_node();
103        let arrow = find_node_by_kind(root, "arrow_function");
104        assert!(arrow.is_some(), "expected to find arrow_function");
105    }
106
107    #[test]
108    fn test_class_declaration() {
109        let src = "class Foo extends Bar { method() {} }";
110        let tree = parse_js(src);
111        let root = tree.root_node();
112        let class = find_node_by_kind(root, "class_declaration");
113        assert!(class.is_some(), "expected to find class_declaration");
114    }
115
116    #[test]
117    fn test_es_import() {
118        let src = "import {x} from 'module';";
119        let tree = parse_js(src);
120        let root = tree.root_node();
121        let import = find_node_by_kind(root, "import_statement");
122        assert!(import.is_some(), "expected to find import_statement");
123    }
124
125    #[test]
126    fn test_commonjs_require() {
127        let src = "const lib = require('lib');";
128        let tree = parse_js(src);
129        let root = tree.root_node();
130        let call = find_node_by_kind(root, "call_expression");
131        assert!(call.is_some(), "expected to find call_expression");
132    }
133}