1use crate::{ImplementsInfo, Import, Visibility};
7use tree_sitter::Node;
8
9pub fn get_visibility(node: &Node, content: &str) -> Visibility {
19 let mut cursor = node.walk();
20 for child in node.children(&mut cursor) {
21 if child.kind() == "accessibility_modifier" {
22 let mod_text = &content[child.byte_range()];
23 return match mod_text {
24 "private" => Visibility::Private,
25 "protected" => Visibility::Protected,
26 "public" => Visibility::Public,
27 _ => Visibility::Public,
28 };
29 }
30 }
31 if let Some(name_node) = node.child_by_field_name("name") {
33 let name = &content[name_node.byte_range()];
34 if name.starts_with('#') {
35 return Visibility::Private;
36 }
37 }
38 Visibility::Public
39}
40
41pub fn build_signature(node: &Node, content: &str, name: &str) -> String {
47 match node.kind() {
48 "method_definition" | "method_signature" => {
49 let params = node
50 .child_by_field_name("parameters")
51 .map(|p| content[p.byte_range()].to_string())
52 .unwrap_or_else(|| "()".to_string());
53 format!("{}{}", name, params)
54 }
55 "function_declaration" | "generator_function_declaration" => {
56 let params = node
57 .child_by_field_name("parameters")
58 .map(|p| content[p.byte_range()].to_string())
59 .unwrap_or_else(|| "()".to_string());
60 format!("function {}{}", name, params)
61 }
62 "class_declaration" | "class" => format!("class {}", name),
63 "interface_declaration" => format!("interface {}", name),
64 "type_alias_declaration" => format!("type {}", name),
65 "enum_declaration" => format!("enum {}", name),
66 _ => {
67 let text = &content[node.byte_range()];
68 text.lines().next().unwrap_or(text).trim().to_string()
69 }
70 }
71}
72
73pub fn extract_implements(node: &Node, content: &str) -> ImplementsInfo {
75 let mut implements = Vec::new();
76 for i in 0..node.child_count() as u32 {
77 if let Some(heritage) = node.child(i)
78 && heritage.kind() == "class_heritage"
79 {
80 for j in 0..heritage.child_count() as u32 {
81 if let Some(clause) = heritage.child(j) {
82 if clause.kind() == "extends_clause" || clause.kind() == "implements_clause" {
83 for k in 0..clause.child_count() as u32 {
84 if let Some(type_node) = clause.child(k)
85 && (type_node.kind() == "type_identifier"
86 || type_node.kind() == "identifier")
87 {
88 implements.push(content[type_node.byte_range()].to_string());
89 }
90 }
91 } else if clause.kind() == "type_identifier" || clause.kind() == "identifier" {
92 implements.push(content[clause.byte_range()].to_string());
93 }
94 }
95 }
96 }
97 }
98 ImplementsInfo {
99 is_interface: false,
100 implements,
101 }
102}
103
104pub fn extract_jsdoc(node: &Node, content: &str) -> Option<String> {
112 let mut prev = node.prev_sibling();
113 while let Some(sibling) = prev {
114 match sibling.kind() {
115 "comment" => {
116 let text = &content[sibling.byte_range()];
117 if text.starts_with("/**") {
118 return Some(clean_block_doc_comment(text));
119 }
120 return None;
121 }
122 "decorator" | "export_statement" => {}
123 _ => return None,
124 }
125 prev = sibling.prev_sibling();
126 }
127 None
128}
129
130fn clean_block_doc_comment(text: &str) -> String {
132 let lines: Vec<&str> = text
133 .strip_prefix("/**")
134 .unwrap_or(text)
135 .strip_suffix("*/")
136 .unwrap_or(text)
137 .lines()
138 .map(|l| l.trim().strip_prefix('*').unwrap_or(l).trim())
139 .filter(|l| !l.is_empty())
140 .collect();
141 lines.join(" ")
142}
143
144pub fn extract_decorators(node: &Node, content: &str) -> Vec<String> {
148 let mut attrs = Vec::new();
149 let mut prev = node.prev_sibling();
150 while let Some(sibling) = prev {
151 if sibling.kind() == "decorator" {
152 attrs.insert(0, content[sibling.byte_range()].to_string());
153 } else if sibling.kind() == "comment" {
154 } else {
156 break;
157 }
158 prev = sibling.prev_sibling();
159 }
160 attrs
161}
162
163pub const JS_CONTAINER_KINDS: &[&str] = &["class_declaration", "class"];
168pub const TS_CONTAINER_KINDS: &[&str] = &["class_declaration", "class", "interface_declaration"];
169
170pub const JS_FUNCTION_KINDS: &[&str] = &[
171 "function_declaration",
172 "method_definition",
173 "generator_function_declaration",
174];
175pub const TS_FUNCTION_KINDS: &[&str] = &[
176 "function_declaration",
177 "method_definition",
178 "method_signature", ];
180
181pub const JS_TYPE_KINDS: &[&str] = &["class_declaration"];
182pub const TS_TYPE_KINDS: &[&str] = &[
183 "class_declaration",
184 "interface_declaration",
185 "type_alias_declaration",
186 "enum_declaration",
187];
188
189pub const IMPORT_KINDS: &[&str] = &["import_statement"];
190pub const PUBLIC_SYMBOL_KINDS: &[&str] = &["export_statement"];
191
192pub const SCOPE_CREATING_KINDS: &[&str] = &[
193 "for_statement",
194 "for_in_statement",
195 "while_statement",
196 "do_statement",
197 "try_statement",
198 "catch_clause",
199 "switch_statement",
200 "arrow_function",
201];
202
203pub const CONTROL_FLOW_KINDS: &[&str] = &[
204 "if_statement",
205 "for_statement",
206 "for_in_statement",
207 "while_statement",
208 "do_statement",
209 "switch_statement",
210 "try_statement",
211 "return_statement",
212 "break_statement",
213 "continue_statement",
214 "throw_statement",
215];
216
217pub const COMPLEXITY_NODES: &[&str] = &[
218 "if_statement",
219 "for_statement",
220 "for_in_statement",
221 "while_statement",
222 "do_statement",
223 "switch_case",
224 "catch_clause",
225 "ternary_expression",
226 "binary_expression",
227];
228
229pub const NESTING_NODES: &[&str] = &[
230 "if_statement",
231 "for_statement",
232 "for_in_statement",
233 "while_statement",
234 "do_statement",
235 "switch_statement",
236 "try_statement",
237 "function_declaration",
238 "method_definition",
239 "class_declaration",
240];
241
242pub fn extract_imports(node: &Node, content: &str) -> Vec<Import> {
248 if node.kind() != "import_statement" {
249 return Vec::new();
250 }
251
252 let line = node.start_position().row + 1;
253 let mut module = String::new();
254 let mut names = Vec::new();
255
256 let mut cursor = node.walk();
257 for child in node.children(&mut cursor) {
258 match child.kind() {
259 "string" | "string_fragment" => {
260 let text = &content[child.byte_range()];
261 module = text.trim_matches(|c| c == '"' || c == '\'').to_string();
262 }
263 "import_clause" => {
264 collect_import_names(&child, content, &mut names);
265 }
266 _ => {}
267 }
268 }
269
270 if module.is_empty() {
271 return Vec::new();
272 }
273
274 if names.len() == 1
277 && let Some(ns_alias) = names[0].strip_prefix("__namespace__:")
278 {
279 return vec![Import {
280 module: module.clone(),
281 names: Vec::new(),
282 alias: Some(ns_alias.to_string()),
283 is_wildcard: true,
284 is_relative: module.starts_with('.'),
285 line,
286 }];
287 }
288
289 vec![Import {
290 module: module.clone(),
291 names,
292 alias: None,
293 is_wildcard: false,
294 is_relative: module.starts_with('.'),
295 line,
296 }]
297}
298
299pub fn format_import(import: &Import, names: Option<&[&str]>) -> String {
301 let names_to_use: Vec<&str> = names
302 .map(|n| n.to_vec())
303 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
304
305 if import.is_wildcard {
306 format!("import * from '{}';", import.module)
307 } else if names_to_use.is_empty() {
308 format!("import '{}';", import.module)
309 } else if names_to_use.len() == 1 {
310 format!("import {{ {} }} from '{}';", names_to_use[0], import.module)
311 } else {
312 format!(
313 "import {{ {} }} from '{}';",
314 names_to_use.join(", "),
315 import.module
316 )
317 }
318}
319
320fn collect_import_names(import_clause: &Node, content: &str, names: &mut Vec<String>) {
321 let mut cursor = import_clause.walk();
322 for child in import_clause.children(&mut cursor) {
323 match child.kind() {
324 "identifier" => {
325 names.push(content[child.byte_range()].to_string());
327 }
328 "named_imports" => {
329 let mut inner_cursor = child.walk();
331 for inner in child.children(&mut inner_cursor) {
332 if inner.kind() == "import_specifier"
333 && let Some(name_node) = inner.child_by_field_name("name")
334 {
335 names.push(content[name_node.byte_range()].to_string());
336 }
337 }
338 }
339 "namespace_import" => {
340 if let Some(name_node) = child.child_by_field_name("name") {
345 names.push(format!(
346 "__namespace__:{}",
347 &content[name_node.byte_range()]
348 ));
349 }
350 }
351 _ => {}
352 }
353 }
354}
355
356pub fn extract_js_module_doc(src: &str) -> Option<String> {
363 let trimmed = if src.starts_with("#!") {
365 src[src.find('\n').map(|i| i + 1).unwrap_or(src.len())..].trim_start()
366 } else {
367 src.trim_start()
368 };
369
370 if !trimmed.starts_with("/**") {
372 return None;
373 }
374
375 let end = trimmed.find("*/")?;
376 let block = &trimmed[..end + 2];
377 let doc = clean_block_doc_comment(block);
378 if doc.is_empty() { None } else { Some(doc) }
379}