codemov_parser/
typescript.rs1use codemov_core::{ImportEdge, ImportKind, Language, Symbol, SymbolKind};
2use tree_sitter::Node;
3
4use crate::ParseError;
5
6pub fn extract(source: &[u8], language: Language) -> Result<Vec<Symbol>, ParseError> {
7 let mut parser = tree_sitter::Parser::new();
8 let lang = match language {
9 Language::TypeScript => tree_sitter_typescript::language_typescript(),
10 _ => tree_sitter_typescript::language_tsx(),
11 };
12 parser
13 .set_language(&lang)
14 .map_err(|e| ParseError::Parse(e.to_string()))?;
15
16 let tree = parser
17 .parse(source, None)
18 .ok_or_else(|| ParseError::Parse("tree-sitter returned None".into()))?;
19
20 let mut symbols = Vec::new();
21 walk(tree.root_node(), source, false, &mut symbols);
22 Ok(symbols)
23}
24
25fn walk(node: Node, source: &[u8], inside_export: bool, out: &mut Vec<Symbol>) {
26 match node.kind() {
27 "function_declaration" | "function" | "generator_function_declaration" => {
28 if let Some(sym) = named(node, source, SymbolKind::Function, "name") {
29 out.push(sym);
30 return; }
32 }
33 "class_declaration" | "abstract_class_declaration" => {
34 if let Some(sym) = named(node, source, SymbolKind::Class, "name") {
35 out.push(sym);
36 return;
37 }
38 }
39 "interface_declaration" => {
40 if let Some(sym) = named(node, source, SymbolKind::Interface, "name") {
41 out.push(sym);
42 return;
43 }
44 }
45 "type_alias_declaration" => {
46 if let Some(sym) = named(node, source, SymbolKind::TypeAlias, "name") {
47 out.push(sym);
48 return;
49 }
50 }
51 "lexical_declaration" | "variable_declaration" => {
52 extract_variable_decl(node, source, inside_export, out);
53 return;
54 }
55 "export_statement" => {
56 let mut cursor = node.walk();
57 for child in node.children(&mut cursor) {
58 walk(child, source, true, out);
59 }
60 return;
61 }
62 _ => {}
63 }
64
65 let mut cursor = node.walk();
66 for child in node.children(&mut cursor) {
67 walk(child, source, inside_export, out);
68 }
69}
70
71fn extract_variable_decl(node: Node, source: &[u8], inside_export: bool, out: &mut Vec<Symbol>) {
72 let mut cursor = node.walk();
73 for child in node.children(&mut cursor) {
74 if child.kind() == "variable_declarator" {
75 if let Some(name_node) = child.child_by_field_name("name") {
76 let value = child.child_by_field_name("value");
77 let kind = match value.map(|v| v.kind()) {
78 Some("arrow_function") | Some("function") => SymbolKind::Function,
79 _ if inside_export => SymbolKind::Export,
80 _ => continue,
81 };
82 if let Ok(name) = name_node.utf8_text(source) {
83 out.push(Symbol {
84 name: name.to_string(),
85 kind,
86 start_line: node.start_position().row as u32 + 1,
87 end_line: node.end_position().row as u32 + 1,
88 });
89 }
90 }
91 }
92 }
93}
94
95fn named(node: Node, source: &[u8], kind: SymbolKind, field: &str) -> Option<Symbol> {
96 let name = node
97 .child_by_field_name(field)?
98 .utf8_text(source)
99 .ok()?
100 .to_string();
101 Some(Symbol {
102 name,
103 kind,
104 start_line: node.start_position().row as u32 + 1,
105 end_line: node.end_position().row as u32 + 1,
106 })
107}
108
109pub fn extract_imports(
110 source: &[u8],
111 language: Language,
112) -> Result<Vec<ImportEdge>, crate::ParseError> {
113 let mut parser = tree_sitter::Parser::new();
114 let lang = match language {
115 Language::TypeScript => tree_sitter_typescript::language_typescript(),
116 _ => tree_sitter_typescript::language_tsx(),
117 };
118 parser
119 .set_language(&lang)
120 .map_err(|e| crate::ParseError::Parse(e.to_string()))?;
121 let tree = parser
122 .parse(source, None)
123 .ok_or_else(|| crate::ParseError::Parse("tree-sitter returned None".into()))?;
124
125 let mut edges = Vec::new();
126 collect_import_nodes(tree.root_node(), source, &mut edges);
127 Ok(edges)
128}
129
130fn collect_import_nodes(node: Node, source: &[u8], out: &mut Vec<ImportEdge>) {
131 match node.kind() {
132 "import_statement" => {
133 if let Some(src_node) = node.child_by_field_name("source") {
134 if let Ok(raw) = src_node.utf8_text(source) {
135 let target = raw.trim_matches(|c| c == '\'' || c == '"').to_string();
136 out.push(ImportEdge {
137 source_path: std::path::PathBuf::new(),
138 target_raw: target,
139 resolved_path: None,
140 kind: ImportKind::Import,
141 line: node.start_position().row as u32 + 1,
142 });
143 }
144 }
145 }
146 "export_statement" => {
147 if let Some(src_node) = node.child_by_field_name("source") {
149 if let Ok(raw) = src_node.utf8_text(source) {
150 let target = raw.trim_matches(|c| c == '\'' || c == '"').to_string();
151 out.push(ImportEdge {
152 source_path: std::path::PathBuf::new(),
153 target_raw: target,
154 resolved_path: None,
155 kind: ImportKind::Export,
156 line: node.start_position().row as u32 + 1,
157 });
158 }
159 }
160 }
161 "call_expression" => {
162 if let Some(fn_node) = node.child_by_field_name("function") {
164 if fn_node.utf8_text(source).ok() == Some("require") {
165 if let Some(args) = node.child_by_field_name("arguments") {
166 let mut cur = args.walk();
167 for child in args.children(&mut cur) {
168 if matches!(child.kind(), "string" | "template_string") {
169 if let Ok(raw) = child.utf8_text(source) {
170 let target =
171 raw.trim_matches(|c| c == '\'' || c == '"').to_string();
172 out.push(ImportEdge {
173 source_path: std::path::PathBuf::new(),
174 target_raw: target,
175 resolved_path: None,
176 kind: ImportKind::Require,
177 line: node.start_position().row as u32 + 1,
178 });
179 }
180 }
181 }
182 }
183 }
184 }
185 }
186 _ => {}
187 }
188
189 let mut cursor = node.walk();
190 for child in node.children(&mut cursor) {
191 collect_import_nodes(child, source, out);
192 }
193}