1use oxc_ast::ast;
7
8use crate::ir;
9use crate::parse::classify::classify_interface;
10use crate::parse::docs::DocComments;
11use crate::parse::members::{convert_class_element, convert_ts_signature};
12use crate::parse::types::{convert_formal_params, convert_type_params};
13use crate::util::diagnostics::DiagnosticCollector;
14use crate::util::naming::to_snake_case;
15
16pub fn convert_class_decl(
17 class: &ast::Class<'_>,
18 ctx: &ir::ModuleContext,
19 docs: &DocComments<'_>,
20 diag: &mut DiagnosticCollector,
21) -> Option<ir::ClassDecl> {
22 let name = class.id.as_ref()?.name.to_string();
23 let js_name = name.clone();
24
25 let type_params = convert_type_params(class.type_parameters.as_ref(), diag);
26
27 let extends = class
28 .super_class
29 .as_ref()
30 .and_then(|sc| match expression_to_dotted_name(sc) {
31 Some(name) => Some(ir::TypeRef::Named(name)),
32 None => {
33 diag.warn("Complex super class expression is not supported");
34 None
35 }
36 });
37
38 let implements: Vec<ir::TypeRef> = class
39 .implements
40 .iter()
41 .map(|i| convert_ts_type_name_to_ref(&i.expression))
42 .collect();
43
44 let is_abstract = class.r#abstract;
45
46 let members: Vec<ir::Member> = class
47 .body
48 .body
49 .iter()
50 .flat_map(|elem| convert_class_element(elem, docs, diag))
51 .collect();
52
53 Some(ir::ClassDecl {
54 name,
55 js_name,
56 type_params,
57 extends,
58 implements,
59 is_abstract,
60 members,
61 type_module_context: ctx.clone(),
62 })
63}
64
65pub fn convert_interface_decl(
66 iface: &ast::TSInterfaceDeclaration<'_>,
67 docs: &DocComments<'_>,
68 diag: &mut DiagnosticCollector,
69) -> ir::InterfaceDecl {
70 let name = iface.id.name.to_string();
71 let js_name = name.clone();
72
73 let type_params = convert_type_params(iface.type_parameters.as_ref(), diag);
74
75 let extends: Vec<ir::TypeRef> = iface
76 .extends
77 .iter()
78 .map(|ext| convert_ts_type_from_heritage(&ext.expression, diag))
79 .collect();
80
81 let members: Vec<ir::Member> = iface
82 .body
83 .body
84 .iter()
85 .flat_map(|sig| convert_ts_signature(sig, docs, diag))
86 .collect();
87
88 let classification = classify_interface(&members);
89
90 ir::InterfaceDecl {
91 name,
92 js_name,
93 type_params,
94 extends,
95 members,
96 classification,
97 }
98}
99
100pub fn convert_function_decl(
101 func: &ast::Function<'_>,
102 diag: &mut DiagnosticCollector,
103) -> Option<ir::FunctionDecl> {
104 let name = func.id.as_ref()?.name.to_string();
105 let js_name = name.clone();
106 let rust_name = to_snake_case(&name);
107
108 let type_params = convert_type_params(func.type_parameters.as_ref(), diag);
109
110 let scope: std::collections::HashSet<&str> = func
112 .type_parameters
113 .as_ref()
114 .map(|tp| tp.params.iter().map(|p| p.name.name.as_str()).collect())
115 .unwrap_or_default();
116
117 let params = convert_formal_params(&func.params, diag);
118 let return_type = func
119 .return_type
120 .as_ref()
121 .map(|rt| crate::parse::types::convert_ts_type_scoped(&rt.type_annotation, &scope, diag))
122 .unwrap_or(ir::TypeRef::Void);
123
124 Some(ir::FunctionDecl {
125 name: rust_name,
126 js_name,
127 type_params,
128 params,
129 return_type,
130 overloads: vec![],
131 })
132}
133
134pub fn convert_string_enum(name: &str, ts_type: &ast::TSType<'_>) -> Option<ir::StringEnumDecl> {
135 let variants = match ts_type {
136 ast::TSType::TSUnionType(union) => union
137 .types
138 .iter()
139 .filter_map(|t| {
140 if let ast::TSType::TSLiteralType(lit) = t {
141 if let ast::TSLiteral::StringLiteral(s) = &lit.literal {
142 let js_value = s.value.to_string();
143 let rust_name = crate::util::naming::to_enum_variant(&js_value);
144 return Some(ir::StringEnumVariant {
145 rust_name,
146 js_value,
147 });
148 }
149 }
150 None
151 })
152 .collect(),
153 ast::TSType::TSLiteralType(lit) => {
154 if let ast::TSLiteral::StringLiteral(s) = &lit.literal {
155 let js_value = s.value.to_string();
156 let rust_name = crate::util::naming::to_enum_variant(&js_value);
157 vec![ir::StringEnumVariant {
158 rust_name,
159 js_value,
160 }]
161 } else {
162 return None;
163 }
164 }
165 _ => return None,
166 };
167
168 let mut rust_names: Vec<String> = variants.iter().map(|v| v.rust_name.clone()).collect();
170 crate::util::naming::dedup_names(&mut rust_names);
171 let variants: Vec<_> = variants
172 .into_iter()
173 .zip(rust_names)
174 .map(|(mut v, name)| {
175 v.rust_name = name;
176 v
177 })
178 .collect();
179
180 Some(ir::StringEnumDecl {
181 name: name.to_string(),
182 variants,
183 })
184}
185
186pub fn classify_ts_enum_kind(enum_decl: &ast::TSEnumDeclaration<'_>) -> ir::RegisteredKind {
188 let mut has_string = false;
189 let mut has_numeric = false;
190
191 for member in &enum_decl.body.members {
192 match &member.initializer {
193 Some(ast::Expression::StringLiteral(_)) => has_string = true,
194 Some(ast::Expression::NumericLiteral(_)) => has_numeric = true,
195 Some(ast::Expression::UnaryExpression(_)) => has_numeric = true,
196 None => {
197 has_numeric = true;
198 }
199 _ => {}
200 }
201 }
202
203 if has_string && !has_numeric {
204 ir::RegisteredKind::StringEnum
205 } else if has_numeric {
206 ir::RegisteredKind::NumericEnum
207 } else {
208 ir::RegisteredKind::StringEnum
209 }
210}
211
212pub fn convert_string_ts_enum(enum_decl: &ast::TSEnumDeclaration<'_>) -> ir::StringEnumDecl {
214 let name = enum_decl.id.name.to_string();
215 let variants: Vec<_> = enum_decl
216 .body
217 .members
218 .iter()
219 .filter_map(|member| {
220 let member_name = match &member.id {
221 ast::TSEnumMemberName::Identifier(id) => id.name.to_string(),
222 ast::TSEnumMemberName::String(s) => s.value.to_string(),
223 _ => return None,
224 };
225 let js_value = match &member.initializer {
226 Some(ast::Expression::StringLiteral(s)) => s.value.to_string(),
227 _ => member_name.clone(),
228 };
229 let rust_name = crate::util::naming::to_enum_variant(&member_name);
230 Some(ir::StringEnumVariant {
231 rust_name,
232 js_value,
233 })
234 })
235 .collect();
236
237 let mut rust_names: Vec<String> = variants.iter().map(|v| v.rust_name.clone()).collect();
238 crate::util::naming::dedup_names(&mut rust_names);
239 let variants: Vec<_> = variants
240 .into_iter()
241 .zip(rust_names)
242 .map(|(mut v, name)| {
243 v.rust_name = name;
244 v
245 })
246 .collect();
247
248 ir::StringEnumDecl { name, variants }
249}
250
251pub fn convert_numeric_enum(
253 enum_decl: &ast::TSEnumDeclaration<'_>,
254 docs: &DocComments<'_>,
255 diag: &mut DiagnosticCollector,
256) -> ir::NumericEnumDecl {
257 let name = enum_decl.id.name.to_string();
258 let mut next_value: i64 = 0;
259
260 let variants: Vec<_> = enum_decl
261 .body
262 .members
263 .iter()
264 .filter_map(|member| {
265 let member_name = match &member.id {
266 ast::TSEnumMemberName::Identifier(id) => id.name.to_string(),
267 ast::TSEnumMemberName::String(s) => s.value.to_string(),
268 _ => return None,
269 };
270
271 let value = match &member.initializer {
272 Some(ast::Expression::NumericLiteral(n)) => {
273 let v = f64_to_i64(n.value, &member_name, &name, diag);
274 next_value = v + 1;
275 v
276 }
277 Some(ast::Expression::UnaryExpression(unary)) => {
278 if let ast::Expression::NumericLiteral(n) = &unary.argument {
279 let raw = f64_to_i64(n.value, &member_name, &name, diag);
280 let v = match unary.operator.as_str() {
281 "-" => -raw,
282 "~" => !raw,
283 _ => raw,
284 };
285 next_value = v + 1;
286 v
287 } else {
288 let v = next_value;
289 next_value += 1;
290 v
291 }
292 }
293 None => {
294 let v = next_value;
295 next_value += 1;
296 v
297 }
298 _ => {
299 let v = next_value;
300 next_value += 1;
301 v
302 }
303 };
304
305 let doc = docs.for_span(member.span.start);
306 let rust_name = crate::util::naming::to_enum_variant(&member_name);
307
308 Some(ir::NumericEnumVariant {
309 rust_name,
310 js_name: member_name,
311 value,
312 doc,
313 })
314 })
315 .collect();
316
317 let mut rust_names: Vec<String> = variants.iter().map(|v| v.rust_name.clone()).collect();
318 crate::util::naming::dedup_names(&mut rust_names);
319 let variants: Vec<_> = variants
320 .into_iter()
321 .zip(rust_names)
322 .map(|(mut v, name)| {
323 v.rust_name = name;
324 v
325 })
326 .collect();
327
328 ir::NumericEnumDecl { name, variants }
329}
330
331fn convert_ts_type_from_heritage(
332 expr: &ast::Expression<'_>,
333 diag: &mut DiagnosticCollector,
334) -> ir::TypeRef {
335 match expression_to_dotted_name(expr) {
336 Some(name) => ir::TypeRef::Named(name),
337 None => {
338 diag.warn("Unsupported heritage expression, falling back to Object");
339 ir::TypeRef::Named("Object".to_string())
340 }
341 }
342}
343
344pub fn expression_to_dotted_name(expr: &ast::Expression<'_>) -> Option<String> {
346 match expr {
347 ast::Expression::Identifier(ident) => Some(ident.name.to_string()),
348 ast::Expression::StaticMemberExpression(member) => {
349 let left = expression_to_dotted_name(&member.object)?;
350 Some(format!("{left}.{}", member.property.name))
351 }
352 _ => None,
353 }
354}
355
356fn convert_ts_type_name_to_ref(type_name: &ast::TSTypeName<'_>) -> ir::TypeRef {
357 match type_name {
358 ast::TSTypeName::IdentifierReference(ident) => ir::TypeRef::Named(ident.name.to_string()),
359 ast::TSTypeName::QualifiedName(qualified) => {
360 let left = convert_ts_type_name_to_string(&qualified.left);
361 let right = &qualified.right.name;
362 ir::TypeRef::Named(format!("{left}.{right}"))
363 }
364 ast::TSTypeName::ThisExpression(_) => ir::TypeRef::Unresolved("this".to_string()),
365 }
366}
367
368pub fn export_default_kind_name(kind: &ast::ExportDefaultDeclarationKind<'_>) -> &'static str {
370 match kind {
371 ast::ExportDefaultDeclarationKind::ClassDeclaration(_) => "ClassDeclaration",
372 ast::ExportDefaultDeclarationKind::FunctionDeclaration(_) => "FunctionDeclaration",
373 ast::ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => "TSInterfaceDeclaration",
374 _ => "Expression",
375 }
376}
377
378fn f64_to_i64(
381 value: f64,
382 member_name: &str,
383 enum_name: &str,
384 diag: &mut DiagnosticCollector,
385) -> i64 {
386 if value.fract() != 0.0 {
387 diag.warn(format!(
388 "Enum `{enum_name}::{member_name}` has non-integer value {value}, truncating to {}",
389 value as i64
390 ));
391 } else if value > i64::MAX as f64 || value < i64::MIN as f64 {
392 diag.warn(format!(
393 "Enum `{enum_name}::{member_name}` value {value} is out of i64 range, truncating"
394 ));
395 }
396 let result = value as i64;
397 if i32::try_from(result).is_err() && u32::try_from(result).is_err() {
399 diag.warn(format!(
400 "Enum `{enum_name}::{member_name}` value {result} exceeds i32/u32 range, \
401 will be truncated in generated code"
402 ));
403 }
404 result
405}
406
407fn convert_ts_type_name_to_string(type_name: &ast::TSTypeName<'_>) -> String {
408 match type_name {
409 ast::TSTypeName::IdentifierReference(ident) => ident.name.to_string(),
410 ast::TSTypeName::QualifiedName(qualified) => {
411 let left = convert_ts_type_name_to_string(&qualified.left);
412 let right = &qualified.right.name;
413 format!("{left}.{right}")
414 }
415 ast::TSTypeName::ThisExpression(_) => "this".to_string(),
416 }
417}