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 if !name.is_empty() {
101 if let Some(col) = line.find("class") {
102 symbols.push(SymbolInfo {
103 name:name.to_string(),
104 kind:SymbolKind::Class,
105 line:line_num,
106 column:col as u32,
107 full_path:format!("{}::{}", file_path.display(), name),
108 });
109 }
110 }
111 }
112
113 if let Some(rest) = line_content.strip_prefix("interface ") {
115 let name = rest.split(|c| c == '{' || c == '<' || c == ' ').next().unwrap_or("").trim();
116 if !name.is_empty() {
117 if let Some(col) = line.find("interface") {
118 symbols.push(SymbolInfo {
119 name:name.to_string(),
120 kind:SymbolKind::Interface,
121 line:line_num,
122 column:col as u32,
123 full_path:format!("{}::{}", file_path.display(), name),
124 });
125 }
126 }
127 }
128
129 if let Some(rest) = line_content.strip_prefix("type ") {
131 let name = rest.split(|c| c == '=' || c == '{' || c == ';').next().unwrap_or("").trim();
133 if !name.is_empty() {
134 if let Some(col) = line.find("type") {
135 symbols.push(SymbolInfo {
136 name:name.to_string(),
137 kind:SymbolKind::TypeParameter,
138 line:line_num,
139 column:col as u32,
140 full_path:format!("{}::{}", file_path.display(), name),
141 });
142 }
143 }
144 }
145
146 if let Some(rest) = line_content.strip_prefix("enum ") {
148 let name = rest.split(|c| c == '{' || c == ';').next().unwrap_or("").trim();
149 if !name.is_empty() {
150 if let Some(col) = line.find("enum") {
151 symbols.push(SymbolInfo {
152 name:name.to_string(),
153 kind:SymbolKind::Enum,
154 line:line_num,
155 column:col as u32,
156 full_path:format!("{}::{}", file_path.display(), name),
157 });
158 }
159 }
160 }
161
162 if let Some(rest) = line_content.strip_prefix("function ") {
164 let name = rest.split('(').next().unwrap_or("").trim();
165 if !name.is_empty() {
166 if !name.contains("=") {
168 if let Some(col) = line.find("function") {
169 symbols.push(SymbolInfo {
170 name:name.to_string(),
171 kind:SymbolKind::Function,
172 line:line_num,
173 column:col as u32,
174 full_path:format!("{}::{}", file_path.display(), name),
175 });
176 }
177 }
178 }
179 }
180
181 if line_content.contains("=>") {
183 if let Some(col) = line.find("=>") {
184 let before_arrow = &line[..col];
185 let name_part = before_arrow.split('=').next().unwrap_or("").trim();
187
188 let func_name = if name_part.contains("(") || name_part.contains("<") {
189 let mut parts = name_part.split(|c| c == '(' || c == '<' || c == ':');
190 let name = parts.next().unwrap_or("").trim();
191 name
192 } else {
193 name_part
194 };
195
196 if !func_name.is_empty() && func_name != "const" && func_name != "let" && func_name != "var" {
198 symbols.push(SymbolInfo {
199 name:func_name.to_string(),
200 kind:SymbolKind::Function,
201 line:line_num,
202 column:col as u32,
203 full_path:format!("{}::{}", file_path.display(), func_name),
204 });
205 }
206 }
207 }
208
209 for kw in &["const ", "let ", "var "] {
211 if let Some(rest) = line_content.strip_prefix(kw) {
212 let name = rest.split(|c| c == '=' || c == ':' || c == ';').next().unwrap_or("").trim();
213 let _is_function_assignment = !line_content.contains("=>")
215 && !line_content.contains("function")
216 && (line_content.contains("=>") || rest.to_lowercase().contains("function"));
217
218 if !name.is_empty() {
219 let kind = if line_content.starts_with("const ") {
221 SymbolKind::Constant
222 } else {
223 SymbolKind::Variable
224 };
225
226 if let Some(col) = line.find(kw) {
227 symbols.push(SymbolInfo {
228 name:name.to_string(),
229 kind,
230 line:line_num,
231 column:col as u32,
232 full_path:format!("{}::{}", file_path.display(), name),
233 });
234 }
235 }
236 }
237 }
238
239 if let Some(rest) = line_content.strip_prefix("namespace ") {
241 let name = rest.split(|c| c == '{' || c == ';').next().unwrap_or("").trim();
242 if !name.is_empty() {
243 if let Some(col) = line.find("namespace") {
244 symbols.push(SymbolInfo {
245 name:name.to_string(),
246 kind:SymbolKind::Namespace,
247 line:line_num,
248 column:col as u32,
249 full_path:format!("{}::{}", file_path.display(), name),
250 });
251 }
252 }
253 }
254
255 symbols
256}
257
258pub fn IsTypeScriptClass(line:&str) -> bool {
260 let trimmed = line.trim();
261 let after_keywords = trimmed
262 .strip_prefix("export ")
263 .or_else(|| trimmed.strip_prefix("default "))
264 .or_else(|| trimmed.strip_prefix("declare "))
265 .unwrap_or(trimmed);
266 after_keywords.starts_with("class ") && !after_keywords.contains(" extends ")
267}
268
269pub fn IsTypeScriptInterface(line:&str) -> bool {
271 let trimmed = line.trim();
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 after_keywords.starts_with("interface ")
278}
279
280pub fn IsTypeScriptFunction(line:&str) -> bool {
282 let trimmed = line.trim();
283 let after_keywords = trimmed
284 .strip_prefix("export ")
285 .or_else(|| trimmed.strip_prefix("default "))
286 .or_else(|| trimmed.strip_prefix("declare "))
287 .or_else(|| trimmed.strip_prefix("async "))
288 .unwrap_or(trimmed);
289 after_keywords.starts_with("function ")
290}
291
292pub fn ExtractExportModifier(line:&str) -> Option<&str> {
294 let trimmed = line.trim();
295 if trimmed.starts_with("export ") {
296 Some("export")
297 } else if trimmed.starts_with("export default ") {
298 Some("export default")
299 } else if trimmed.starts_with("export type ") {
300 Some("export type")
301 } else if trimmed.starts_with("export const ") {
302 Some("export const")
303 } else if trimmed.starts_with("export function ") {
304 Some("export function")
305 } else if trimmed.starts_with("export interface ") {
306 Some("export interface")
307 } else if trimmed.starts_with("export class ") {
308 Some("export class")
309 } else {
310 None
311 }
312}
313
314pub fn ExtractTypeAnnotation(line:&str) -> Option<String> {
316 if let Some(colon_idx) = line.find(':') {
317 let rest = &line[colon_idx + 1..];
318 let end_idx = rest
320 .find(|c| c == '=' || c == '{' || c == ';' || c == ',')
321 .unwrap_or(rest.len());
322 let type_str = rest[..end_idx].trim();
323 if !type_str.is_empty() { Some(type_str.to_string()) } else { None }
324 } else {
325 None
326 }
327}
328
329pub fn ExtractGenericParameters(line:&str) -> Vec<String> {
331 let mut generics = Vec::new();
332 if let Some(start) = line.find('<') {
333 if let Some(end) = line.rfind('>') {
334 let content = &line[start + 1..end];
335 for part in content.split(',') {
337 let trimmed = part.trim();
338 if !trimmed.is_empty() {
339 generics.push(trimmed.to_string());
340 }
341 }
342 }
343 }
344 generics
345}