Skip to main content

normalize_languages/
javascript.rs

1//! JavaScript language support.
2
3use crate::ecmascript;
4use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
5use tree_sitter::Node;
6
7/// JavaScript language support.
8pub struct JavaScript;
9
10impl Language for JavaScript {
11    fn name(&self) -> &'static str {
12        "JavaScript"
13    }
14    fn extensions(&self) -> &'static [&'static str] {
15        &["js", "mjs", "cjs", "jsx"]
16    }
17    fn grammar_name(&self) -> &'static str {
18        "javascript"
19    }
20
21    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
22        Some(self)
23    }
24
25    fn signature_suffix(&self) -> &'static str {
26        " {}"
27    }
28
29    fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
30        ecmascript::extract_jsdoc(node, content)
31    }
32
33    fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
34        ecmascript::extract_implements(node, content)
35    }
36
37    fn build_signature(&self, node: &Node, content: &str) -> String {
38        let name = match self.node_name(node, content) {
39            Some(n) => n,
40            None => {
41                return content[node.byte_range()]
42                    .lines()
43                    .next()
44                    .unwrap_or("")
45                    .trim()
46                    .to_string();
47            }
48        };
49        ecmascript::build_signature(node, content, name)
50    }
51
52    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
53        ecmascript::extract_imports(node, content)
54    }
55
56    fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
57        ecmascript::format_import(import, names)
58    }
59
60    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
61        {
62            let name = symbol.name.as_str();
63            match symbol.kind {
64                crate::SymbolKind::Function | crate::SymbolKind::Method => {
65                    name.starts_with("test_")
66                        || name.starts_with("Test")
67                        || name == "describe"
68                        || name == "it"
69                        || name == "test"
70                }
71                crate::SymbolKind::Module => {
72                    name == "tests" || name == "test" || name == "__tests__"
73                }
74                _ => false,
75            }
76        }
77    }
78
79    fn test_file_globs(&self) -> &'static [&'static str] {
80        &[
81            "**/__tests__/**/*.js",
82            "**/__mocks__/**/*.js",
83            "**/*.test.js",
84            "**/*.spec.js",
85            "**/*.test.jsx",
86            "**/*.spec.jsx",
87        ]
88    }
89
90    fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
91        ecmascript::extract_decorators(node, content)
92    }
93
94    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
95        node.child_by_field_name("body")
96    }
97
98    fn analyze_container_body(
99        &self,
100        body_node: &Node,
101        content: &str,
102        inner_indent: &str,
103    ) -> Option<ContainerBody> {
104        crate::body::analyze_brace_body(body_node, content, inner_indent)
105    }
106
107    fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
108        ecmascript::get_visibility(node, content)
109    }
110
111    fn extract_module_doc(&self, src: &str) -> Option<String> {
112        ecmascript::extract_js_module_doc(src)
113    }
114}
115
116impl LanguageSymbols for JavaScript {}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::validate_unused_kinds_audit;
122
123    /// Documents node kinds that exist in the JavaScript grammar but aren't used in trait methods.
124    /// Run `cross_check_node_kinds` in registry.rs to see all potentially useful kinds.
125    #[test]
126    fn unused_node_kinds_audit() {
127        #[rustfmt::skip]
128        let documented_unused: &[&str] = &[
129            // STRUCTURAL
130            "class_body",              // class body block
131            "class_heritage",          // extends clause
132            "class_static_block",      // static { }
133            "formal_parameters",       // function params
134            "field_definition",        // class field              // too common
135            "private_property_identifier", // #field
136            "property_identifier",     // obj.prop
137            "shorthand_property_identifier", // { x } shorthand
138            "shorthand_property_identifier_pattern", // destructuring shorthand
139            "statement_block",         // { }
140            "statement_identifier",    // label name
141            "switch_body",             // switch cases
142
143            // CLAUSE
144            "else_clause",             // else branch
145            "finally_clause",          // finally block
146
147            // EXPRESSION   // x = y
148            "augmented_assignment_expression", // x += y
149            "await_expression",        // await foo         // foo()     // function() {}       // foo.bar          // new Foo()
150            "parenthesized_expression",// (expr)
151            "sequence_expression",     // a, b
152            "subscript_expression",    // arr[i]
153            "unary_expression",        // -x, !x
154            "update_expression",       // x++
155            "yield_expression",        // yield x
156
157            // IMPORT/EXPORT DETAILS
158            "export_clause",           // export { a, b }
159            "export_specifier",        // export { a as b }
160            "import",                  // import keyword
161            "import_attribute",        // import attributes
162            "import_clause",           // import clause
163            "import_specifier",        // import { a }
164            "named_imports",           // { a, b }
165            "namespace_export",        // export * as ns
166            "namespace_import",        // import * as ns
167
168            // DECLARATION
169            "debugger_statement",      // debugger;
170            "empty_statement",         // ;
171            "expression_statement",    // expr;      // function* foo
172            "labeled_statement",       // label: stmt     // let/const
173            "using_declaration",       // using x = ...    // var x
174            "with_statement",          // with (obj) - deprecated
175
176            // JSX
177            "jsx_expression",          // {expr} in JSX
178            // control flow — not extracted as symbols
179            "break_statement",
180            "while_statement",
181            "throw_statement",
182            "if_statement",
183            "for_statement",
184            "import_statement",
185            "ternary_expression",
186            "catch_clause",
187            "do_statement",
188            "return_statement",
189            "try_statement",
190            "for_in_statement",
191            "continue_statement",
192            "switch_statement",
193            "switch_case",
194            "arrow_function",
195        ];
196
197        validate_unused_kinds_audit(&JavaScript, documented_unused)
198            .expect("JavaScript unused node kinds audit failed");
199    }
200}