AirLibrary/Indexing/Language/
ParseTypeScript.rs1use std::path::PathBuf;
67
68use crate::Indexing::State::CreateState::{SymbolInfo, SymbolKind};
69
70pub fn ExtractTypeScriptSymbols(content:&str, file_path:&PathBuf) -> Vec<SymbolInfo> {
72 let mut symbols = Vec::new();
73
74 let lines:Vec<&str> = content.lines().collect();
75
76 for (line_idx, line) in lines.iter().enumerate() {
77 let line_content = line.trim();
78
79 let line_num = line_idx as u32 + 1;
80
81 if line_content.starts_with("//") || line_content.starts_with("/*") || line_content.starts_with("*") {
83 continue;
84 }
85
86 symbols.extend(ExtractTypeScriptSymbolsFromLine(line_content, line_num, line, file_path));
88 }
89
90 symbols
91}
92
93fn ExtractTypeScriptSymbolsFromLine(line_content:&str, line_num:u32, line:&str, file_path:&PathBuf) -> Vec<SymbolInfo> {
95 let mut symbols = Vec::new();
96
97 if let Some(rest) = line_content.strip_prefix("class ") {
99 let name = rest.split(|c| c == '{' || c == '<' || c == ' ').next().unwrap_or("").trim();
100
101 if !name.is_empty() {
102 if let Some(col) = line.find("class") {
103 symbols.push(SymbolInfo {
104 name:name.to_string(),
105 kind:SymbolKind::Class,
106 line:line_num,
107 column:col as u32,
108 full_path:format!("{}::{}", file_path.display(), name),
109 });
110 }
111 }
112 }
113
114 if let Some(rest) = line_content.strip_prefix("interface ") {
116 let name = rest.split(|c| c == '{' || c == '<' || c == ' ').next().unwrap_or("").trim();
117
118 if !name.is_empty() {
119 if let Some(col) = line.find("interface") {
120 symbols.push(SymbolInfo {
121 name:name.to_string(),
122 kind:SymbolKind::Interface,
123 line:line_num,
124 column:col as u32,
125 full_path:format!("{}::{}", file_path.display(), name),
126 });
127 }
128 }
129 }
130
131 if let Some(rest) = line_content.strip_prefix("type ") {
133 let name = rest.split(|c| c == '=' || c == '{' || c == ';').next().unwrap_or("").trim();
135
136 if !name.is_empty() {
137 if let Some(col) = line.find("type") {
138 symbols.push(SymbolInfo {
139 name:name.to_string(),
140 kind:SymbolKind::TypeParameter,
141 line:line_num,
142 column:col as u32,
143 full_path:format!("{}::{}", file_path.display(), name),
144 });
145 }
146 }
147 }
148
149 if let Some(rest) = line_content.strip_prefix("enum ") {
151 let name = rest.split(|c| c == '{' || c == ';').next().unwrap_or("").trim();
152
153 if !name.is_empty() {
154 if let Some(col) = line.find("enum") {
155 symbols.push(SymbolInfo {
156 name:name.to_string(),
157 kind:SymbolKind::Enum,
158 line:line_num,
159 column:col as u32,
160 full_path:format!("{}::{}", file_path.display(), name),
161 });
162 }
163 }
164 }
165
166 if let Some(rest) = line_content.strip_prefix("function ") {
168 let name = rest.split('(').next().unwrap_or("").trim();
169
170 if !name.is_empty() {
171 if !name.contains("=") {
173 if let Some(col) = line.find("function") {
174 symbols.push(SymbolInfo {
175 name:name.to_string(),
176 kind:SymbolKind::Function,
177 line:line_num,
178 column:col as u32,
179 full_path:format!("{}::{}", file_path.display(), name),
180 });
181 }
182 }
183 }
184 }
185
186 if line_content.contains("=>") {
188 if let Some(col) = line.find("=>") {
189 let before_arrow = &line[..col];
190
191 let name_part = before_arrow.split('=').next().unwrap_or("").trim();
193
194 let func_name = if name_part.contains("(") || name_part.contains("<") {
195 let mut parts = name_part.split(|c| c == '(' || c == '<' || c == ':');
196
197 let name = parts.next().unwrap_or("").trim();
198
199 name
200 } else {
201 name_part
202 };
203
204 if !func_name.is_empty() && func_name != "const" && func_name != "let" && func_name != "var" {
206 symbols.push(SymbolInfo {
207 name:func_name.to_string(),
208 kind:SymbolKind::Function,
209 line:line_num,
210 column:col as u32,
211 full_path:format!("{}::{}", file_path.display(), func_name),
212 });
213 }
214 }
215 }
216
217 for kw in &["const ", "let ", "var "] {
219 if let Some(rest) = line_content.strip_prefix(kw) {
220 let name = rest.split(|c| c == '=' || c == ':' || c == ';').next().unwrap_or("").trim();
221
222 let _is_function_assignment = !line_content.contains("=>")
224 && !line_content.contains("function")
225 && (line_content.contains("=>") || rest.to_lowercase().contains("function"));
226
227 if !name.is_empty() {
228 let kind = if line_content.starts_with("const ") {
230 SymbolKind::Constant
231 } else {
232 SymbolKind::Variable
233 };
234
235 if let Some(col) = line.find(kw) {
236 symbols.push(SymbolInfo {
237 name:name.to_string(),
238 kind,
239 line:line_num,
240 column:col as u32,
241 full_path:format!("{}::{}", file_path.display(), name),
242 });
243 }
244 }
245 }
246 }
247
248 if let Some(rest) = line_content.strip_prefix("namespace ") {
250 let name = rest.split(|c| c == '{' || c == ';').next().unwrap_or("").trim();
251
252 if !name.is_empty() {
253 if let Some(col) = line.find("namespace") {
254 symbols.push(SymbolInfo {
255 name:name.to_string(),
256 kind:SymbolKind::Namespace,
257 line:line_num,
258 column:col as u32,
259 full_path:format!("{}::{}", file_path.display(), name),
260 });
261 }
262 }
263 }
264
265 symbols
266}
267
268pub fn IsTypeScriptClass(line:&str) -> bool {
270 let trimmed = line.trim();
271
272 let after_keywords = trimmed
273 .strip_prefix("export ")
274 .or_else(|| trimmed.strip_prefix("default "))
275 .or_else(|| trimmed.strip_prefix("declare "))
276 .unwrap_or(trimmed);
277
278 after_keywords.starts_with("class ") && !after_keywords.contains(" extends ")
279}
280
281pub fn IsTypeScriptInterface(line:&str) -> bool {
283 let trimmed = line.trim();
284
285 let after_keywords = trimmed
286 .strip_prefix("export ")
287 .or_else(|| trimmed.strip_prefix("default "))
288 .or_else(|| trimmed.strip_prefix("declare "))
289 .unwrap_or(trimmed);
290
291 after_keywords.starts_with("interface ")
292}
293
294pub fn IsTypeScriptFunction(line:&str) -> bool {
296 let trimmed = line.trim();
297
298 let after_keywords = trimmed
299 .strip_prefix("export ")
300 .or_else(|| trimmed.strip_prefix("default "))
301 .or_else(|| trimmed.strip_prefix("declare "))
302 .or_else(|| trimmed.strip_prefix("async "))
303 .unwrap_or(trimmed);
304
305 after_keywords.starts_with("function ")
306}
307
308pub fn ExtractExportModifier(line:&str) -> Option<&str> {
310 let trimmed = line.trim();
311
312 if trimmed.starts_with("export ") {
313 Some("export")
314 } else if trimmed.starts_with("export default ") {
315 Some("export default")
316 } else if trimmed.starts_with("export type ") {
317 Some("export type")
318 } else if trimmed.starts_with("export const ") {
319 Some("export const")
320 } else if trimmed.starts_with("export function ") {
321 Some("export function")
322 } else if trimmed.starts_with("export interface ") {
323 Some("export interface")
324 } else if trimmed.starts_with("export class ") {
325 Some("export class")
326 } else {
327 None
328 }
329}
330
331pub fn ExtractTypeAnnotation(line:&str) -> Option<String> {
333 if let Some(colon_idx) = line.find(':') {
334 let rest = &line[colon_idx + 1..];
335
336 let end_idx = rest
338 .find(|c| c == '=' || c == '{' || c == ';' || c == ',')
339 .unwrap_or(rest.len());
340
341 let type_str = rest[..end_idx].trim();
342
343 if !type_str.is_empty() { Some(type_str.to_string()) } else { None }
344 } else {
345 None
346 }
347}
348
349pub fn ExtractGenericParameters(line:&str) -> Vec<String> {
351 let mut generics = Vec::new();
352
353 if let Some(start) = line.find('<') {
354 if let Some(end) = line.rfind('>') {
355 let content = &line[start + 1..end];
356
357 for part in content.split(',') {
359 let trimmed = part.trim();
360
361 if !trimmed.is_empty() {
362 generics.push(trimmed.to_string());
363 }
364 }
365 }
366 }
367
368 generics
369}