1use crate::ecmascript;
4use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
5use tree_sitter::Node;
6
7pub struct TypeScript;
9
10pub struct Tsx;
12
13impl Language for TypeScript {
14 fn name(&self) -> &'static str {
15 "TypeScript"
16 }
17 fn extensions(&self) -> &'static [&'static str] {
18 &["ts", "mts", "cts"]
19 }
20 fn grammar_name(&self) -> &'static str {
21 "typescript"
22 }
23
24 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
25 Some(self)
26 }
27
28 fn signature_suffix(&self) -> &'static str {
29 " {}"
30 }
31
32 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
33 ecmascript::extract_jsdoc(node, content)
34 }
35
36 fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
37 ecmascript::extract_implements(node, content)
38 }
39
40 fn build_signature(&self, node: &Node, content: &str) -> String {
41 let name = match self.node_name(node, content) {
42 Some(n) => n,
43 None => {
44 return content[node.byte_range()]
45 .lines()
46 .next()
47 .unwrap_or("")
48 .trim()
49 .to_string();
50 }
51 };
52 ecmascript::build_signature(node, content, name)
53 }
54
55 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
56 ecmascript::extract_imports(node, content)
57 }
58
59 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
60 ecmascript::format_import(import, names)
61 }
62
63 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
64 let name = symbol.name.as_str();
65 match symbol.kind {
66 crate::SymbolKind::Function | crate::SymbolKind::Method => {
67 name.starts_with("test_")
68 || name.starts_with("Test")
69 || name == "describe"
70 || name == "it"
71 || name == "test"
72 }
73 crate::SymbolKind::Module => name == "tests" || name == "test" || name == "__tests__",
74 _ => false,
75 }
76 }
77
78 fn test_file_globs(&self) -> &'static [&'static str] {
79 &[
80 "**/__tests__/**/*.ts",
81 "**/__mocks__/**/*.ts",
82 "**/*.test.ts",
83 "**/*.spec.ts",
84 "**/*.test.tsx",
85 "**/*.spec.tsx",
86 ]
87 }
88
89 fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
90 ecmascript::extract_decorators(node, content)
91 }
92
93 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
94 if let Some(body) = node.child_by_field_name("body") {
96 return Some(body);
97 }
98 for i in 0..node.child_count() as u32 {
100 if let Some(child) = node.child(i)
101 && (child.kind() == "interface_body" || child.kind() == "class_body")
102 {
103 return Some(child);
104 }
105 }
106 None
107 }
108
109 fn analyze_container_body(
110 &self,
111 body_node: &Node,
112 content: &str,
113 inner_indent: &str,
114 ) -> Option<ContainerBody> {
115 crate::body::analyze_brace_body(body_node, content, inner_indent)
116 }
117
118 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
119 ecmascript::get_visibility(node, content)
120 }
121
122 fn extract_module_doc(&self, src: &str) -> Option<String> {
123 ecmascript::extract_js_module_doc(src)
124 }
125}
126
127impl LanguageSymbols for TypeScript {}
128
129impl Language for Tsx {
131 fn name(&self) -> &'static str {
132 "TSX"
133 }
134 fn extensions(&self) -> &'static [&'static str] {
135 &["tsx"]
136 }
137 fn grammar_name(&self) -> &'static str {
138 "tsx"
139 }
140
141 fn signature_suffix(&self) -> &'static str {
142 " {}"
143 }
144
145 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
146 ecmascript::extract_jsdoc(node, content)
147 }
148
149 fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
150 ecmascript::extract_implements(node, content)
151 }
152
153 fn build_signature(&self, node: &Node, content: &str) -> String {
154 let name = match self.node_name(node, content) {
155 Some(n) => n,
156 None => {
157 return content[node.byte_range()]
158 .lines()
159 .next()
160 .unwrap_or("")
161 .trim()
162 .to_string();
163 }
164 };
165 ecmascript::build_signature(node, content, name)
166 }
167
168 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
169 ecmascript::extract_imports(node, content)
170 }
171
172 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
173 ecmascript::format_import(import, names)
174 }
175
176 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
177 let name = symbol.name.as_str();
178 match symbol.kind {
179 crate::SymbolKind::Function | crate::SymbolKind::Method => {
180 name.starts_with("test_")
181 || name.starts_with("Test")
182 || name == "describe"
183 || name == "it"
184 || name == "test"
185 }
186 crate::SymbolKind::Module => name == "tests" || name == "test" || name == "__tests__",
187 _ => false,
188 }
189 }
190
191 fn test_file_globs(&self) -> &'static [&'static str] {
192 &[
193 "**/__tests__/**/*.ts",
194 "**/__mocks__/**/*.ts",
195 "**/*.test.ts",
196 "**/*.spec.ts",
197 "**/*.test.tsx",
198 "**/*.spec.tsx",
199 ]
200 }
201
202 fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
203 ecmascript::extract_decorators(node, content)
204 }
205
206 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
207 if let Some(body) = node.child_by_field_name("body") {
209 return Some(body);
210 }
211 for i in 0..node.child_count() as u32 {
213 if let Some(child) = node.child(i)
214 && (child.kind() == "interface_body" || child.kind() == "class_body")
215 {
216 return Some(child);
217 }
218 }
219 None
220 }
221
222 fn analyze_container_body(
223 &self,
224 body_node: &Node,
225 content: &str,
226 inner_indent: &str,
227 ) -> Option<ContainerBody> {
228 crate::body::analyze_brace_body(body_node, content, inner_indent)
229 }
230
231 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
232 ecmascript::get_visibility(node, content)
233 }
234
235 fn extract_module_doc(&self, src: &str) -> Option<String> {
236 ecmascript::extract_js_module_doc(src)
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::validate_unused_kinds_audit;
244
245 #[test]
248 fn unused_node_kinds_audit() {
249 #[rustfmt::skip]
250 let documented_unused: &[&str] = &[
251 "class_body", "class_heritage", "class_static_block", "enum_assignment", "enum_body", "formal_parameters", "identifier", "interface_body", "nested_identifier", "nested_type_identifier", "private_property_identifier", "property_identifier", "public_field_definition", "shorthand_property_identifier", "shorthand_property_identifier_pattern", "statement_block", "statement_identifier", "switch_body", "default_type", "else_clause", "extends_clause", "extends_type_clause", "finally_clause", "implements_clause", "as_expression", "assignment_expression", "augmented_assignment_expression", "await_expression", "call_expression", "function_expression", "instantiation_expression","member_expression", "non_null_expression", "parenthesized_expression","satisfies_expression", "sequence_expression", "subscript_expression", "unary_expression", "update_expression", "yield_expression", "adding_type_annotation", "array_type", "conditional_type", "construct_signature", "constructor_type", "existential_type", "flow_maybe_type", "function_type", "generic_type", "index_type_query", "infer_type", "intersection_type", "literal_type", "lookup_type", "mapped_type_clause", "object_type", "omitting_type_annotation","opting_type_annotation", "optional_type", "override_modifier", "parenthesized_type", "predefined_type", "readonly_type", "rest_type", "template_literal_type", "template_type", "this_type", "tuple_type", "type_arguments", "type_assertion", "type_parameter", "type_parameters", "type_predicate", "type_predicate_annotation", "type_query", "union_type", "accessibility_modifier", "export_clause", "export_specifier", "import", "import_alias", "import_attribute", "import_clause", "import_require_clause", "import_specifier", "named_imports", "namespace_export", "namespace_import", "ambient_declaration", "debugger_statement", "empty_statement", "expression_statement", "generator_function", "generator_function_declaration", "internal_module", "labeled_statement", "lexical_declaration", "using_declaration", "variable_declaration", "with_statement", "for_in_statement",
364 "switch_case",
365 "continue_statement",
366 "do_statement",
367 "return_statement",
368 "class",
369 "switch_statement",
370 "binary_expression",
371 "while_statement",
372 "for_statement",
373 "if_statement",
374 "throw_statement",
375 "try_statement",
376 "break_statement",
377 "arrow_function",
378 "catch_clause",
379 "ternary_expression",
380 "import_statement",
381 "export_statement",
382 ];
383
384 validate_unused_kinds_audit(&TypeScript, documented_unused)
385 .expect("TypeScript unused node kinds audit failed");
386 }
387}