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 let lines:Vec<&str> = content.lines().collect();
74
75 for (line_idx, line) in lines.iter().enumerate() {
76 let line_content = line.trim();
77 let line_num = line_idx as u32 + 1;
78
79 if line_content.starts_with("//") || line_content.starts_with("/*") || line_content.starts_with("*") {
81 continue;
82 }
83
84 symbols.extend(ExtractTypeScriptSymbolsFromLine(line_content, line_num, line, file_path));
86 }
87
88 symbols
89}
90
91fn ExtractTypeScriptSymbolsFromLine(line_content:&str, line_num:u32, line:&str, file_path:&PathBuf) -> Vec<SymbolInfo> {
93 let mut symbols = Vec::new();
94
95 if let Some(rest) = line_content.strip_prefix("class ") {
97 let name = rest.split(|c| c == '{' || c == '<' || c == ' ').next().unwrap_or("").trim();
98 if !name.is_empty() {
99 if let Some(col) = line.find("class") {
100 symbols.push(SymbolInfo {
101 name:name.to_string(),
102 kind:SymbolKind::Class,
103 line:line_num,
104 column:col as u32,
105 full_path:format!("{}::{}", file_path.display(), name),
106 });
107 }
108 }
109 }
110
111 if let Some(rest) = line_content.strip_prefix("interface ") {
113 let name = rest.split(|c| c == '{' || c == '<' || c == ' ').next().unwrap_or("").trim();
114 if !name.is_empty() {
115 if let Some(col) = line.find("interface") {
116 symbols.push(SymbolInfo {
117 name:name.to_string(),
118 kind:SymbolKind::Interface,
119 line:line_num,
120 column:col as u32,
121 full_path:format!("{}::{}", file_path.display(), name),
122 });
123 }
124 }
125 }
126
127 if let Some(rest) = line_content.strip_prefix("type ") {
129 let name = rest.split(|c| c == '=' || c == '{' || c == ';').next().unwrap_or("").trim();
131 if !name.is_empty() {
132 if let Some(col) = line.find("type") {
133 symbols.push(SymbolInfo {
134 name:name.to_string(),
135 kind:SymbolKind::TypeParameter,
136 line:line_num,
137 column:col as u32,
138 full_path:format!("{}::{}", file_path.display(), name),
139 });
140 }
141 }
142 }
143
144 if let Some(rest) = line_content.strip_prefix("enum ") {
146 let name = rest.split(|c| c == '{' || c == ';').next().unwrap_or("").trim();
147 if !name.is_empty() {
148 if let Some(col) = line.find("enum") {
149 symbols.push(SymbolInfo {
150 name:name.to_string(),
151 kind:SymbolKind::Enum,
152 line:line_num,
153 column:col as u32,
154 full_path:format!("{}::{}", file_path.display(), name),
155 });
156 }
157 }
158 }
159
160 if let Some(rest) = line_content.strip_prefix("function ") {
162 let name = rest.split('(').next().unwrap_or("").trim();
163 if !name.is_empty() {
164 if !name.contains("=") {
166 if let Some(col) = line.find("function") {
167 symbols.push(SymbolInfo {
168 name:name.to_string(),
169 kind:SymbolKind::Function,
170 line:line_num,
171 column:col as u32,
172 full_path:format!("{}::{}", file_path.display(), name),
173 });
174 }
175 }
176 }
177 }
178
179 if line_content.contains("=>") {
181 if let Some(col) = line.find("=>") {
182 let before_arrow = &line[..col];
183 let name_part = before_arrow.split('=').next().unwrap_or("").trim();
185
186 let func_name = if name_part.contains("(") || name_part.contains("<") {
187 let mut parts = name_part.split(|c| c == '(' || c == '<' || c == ':');
188 let name = parts.next().unwrap_or("").trim();
189 name
190 } else {
191 name_part
192 };
193
194 if !func_name.is_empty() && func_name != "const" && func_name != "let" && func_name != "var" {
196 symbols.push(SymbolInfo {
197 name:func_name.to_string(),
198 kind:SymbolKind::Function,
199 line:line_num,
200 column:col as u32,
201 full_path:format!("{}::{}", file_path.display(), func_name),
202 });
203 }
204 }
205 }
206
207 for kw in &["const ", "let ", "var "] {
209 if let Some(rest) = line_content.strip_prefix(kw) {
210 let name = rest.split(|c| c == '=' || c == ':' || c == ';').next().unwrap_or("").trim();
211 let is_function_assignment = !line_content.contains("=>")
213 && !line_content.contains("function")
214 && (line_content.contains("=>") || rest.to_lowercase().contains("function"));
215
216 if !name.is_empty() {
217 let kind = if line_content.starts_with("const ") {
219 SymbolKind::Constant
220 } else {
221 SymbolKind::Variable
222 };
223
224 if let Some(col) = line.find(kw) {
225 symbols.push(SymbolInfo {
226 name:name.to_string(),
227 kind,
228 line:line_num,
229 column:col as u32,
230 full_path:format!("{}::{}", file_path.display(), name),
231 });
232 }
233 }
234 }
235 }
236
237 if let Some(rest) = line_content.strip_prefix("namespace ") {
239 let name = rest.split(|c| c == '{' || c == ';').next().unwrap_or("").trim();
240 if !name.is_empty() {
241 if let Some(col) = line.find("namespace") {
242 symbols.push(SymbolInfo {
243 name:name.to_string(),
244 kind:SymbolKind::Namespace,
245 line:line_num,
246 column:col as u32,
247 full_path:format!("{}::{}", file_path.display(), name),
248 });
249 }
250 }
251 }
252
253 symbols
254}
255
256pub fn IsTypeScriptClass(line:&str) -> bool {
258 let trimmed = line.trim();
259 let after_keywords = trimmed
260 .strip_prefix("export ")
261 .or_else(|| trimmed.strip_prefix("default "))
262 .or_else(|| trimmed.strip_prefix("declare "))
263 .unwrap_or(trimmed);
264 after_keywords.starts_with("class ") && !after_keywords.contains(" extends ")
265}
266
267pub fn IsTypeScriptInterface(line:&str) -> bool {
269 let trimmed = line.trim();
270 let after_keywords = trimmed
271 .strip_prefix("export ")
272 .or_else(|| trimmed.strip_prefix("default "))
273 .or_else(|| trimmed.strip_prefix("declare "))
274 .unwrap_or(trimmed);
275 after_keywords.starts_with("interface ")
276}
277
278pub fn IsTypeScriptFunction(line:&str) -> bool {
280 let trimmed = line.trim();
281 let after_keywords = trimmed
282 .strip_prefix("export ")
283 .or_else(|| trimmed.strip_prefix("default "))
284 .or_else(|| trimmed.strip_prefix("declare "))
285 .or_else(|| trimmed.strip_prefix("async "))
286 .unwrap_or(trimmed);
287 after_keywords.starts_with("function ")
288}
289
290pub fn ExtractExportModifier(line:&str) -> Option<&str> {
292 let trimmed = line.trim();
293 if trimmed.starts_with("export ") {
294 Some("export")
295 } else if trimmed.starts_with("export default ") {
296 Some("export default")
297 } else if trimmed.starts_with("export type ") {
298 Some("export type")
299 } else if trimmed.starts_with("export const ") {
300 Some("export const")
301 } else if trimmed.starts_with("export function ") {
302 Some("export function")
303 } else if trimmed.starts_with("export interface ") {
304 Some("export interface")
305 } else if trimmed.starts_with("export class ") {
306 Some("export class")
307 } else {
308 None
309 }
310}
311
312pub fn ExtractTypeAnnotation(line:&str) -> Option<String> {
314 if let Some(colon_idx) = line.find(':') {
315 let rest = &line[colon_idx + 1..];
316 let end_idx = rest
318 .find(|c| c == '=' || c == '{' || c == ';' || c == ',')
319 .unwrap_or(rest.len());
320 let type_str = rest[..end_idx].trim();
321 if !type_str.is_empty() { Some(type_str.to_string()) } else { None }
322 } else {
323 None
324 }
325}
326
327pub fn ExtractGenericParameters(line:&str) -> Vec<String> {
329 let mut generics = Vec::new();
330 if let Some(start) = line.find('<') {
331 if let Some(end) = line.rfind('>') {
332 let content = &line[start + 1..end];
333 for part in content.split(',') {
335 let trimmed = part.trim();
336 if !trimmed.is_empty() {
337 generics.push(trimmed.to_string());
338 }
339 }
340 }
341 }
342 generics
343}