1use std::collections::HashSet;
4
5use oxc_ast::ast::*;
6
7use crate::ir::TypeRef;
8use crate::util::diagnostics::DiagnosticCollector;
9use crate::util::naming::to_snake_case;
10
11pub type TypeParamScope<'a> = HashSet<&'a str>;
14
15pub fn convert_ts_type(ts_type: &TSType<'_>, diag: &mut DiagnosticCollector) -> TypeRef {
20 convert_ts_type_scoped(ts_type, &HashSet::new(), diag)
21}
22
23pub fn convert_ts_type_scoped(
28 ts_type: &TSType<'_>,
29 scope: &TypeParamScope<'_>,
30 diag: &mut DiagnosticCollector,
31) -> TypeRef {
32 let ts_type = ts_type.without_parenthesized();
34
35 match ts_type {
36 TSType::TSAnyKeyword(_) => TypeRef::Any,
38 TSType::TSBooleanKeyword(_) => TypeRef::Boolean,
39 TSType::TSBigIntKeyword(_) => TypeRef::BigInt,
40 TSType::TSNeverKeyword(_) => TypeRef::Any, TSType::TSNullKeyword(_) => TypeRef::Null,
42 TSType::TSNumberKeyword(_) => TypeRef::Number,
43 TSType::TSObjectKeyword(_) => TypeRef::Object,
44 TSType::TSStringKeyword(_) => TypeRef::String,
45 TSType::TSSymbolKeyword(_) => TypeRef::Symbol,
46 TSType::TSUndefinedKeyword(_) => TypeRef::Undefined,
47 TSType::TSUnknownKeyword(_) => TypeRef::Unknown,
48 TSType::TSVoidKeyword(_) => TypeRef::Void,
49 TSType::TSIntrinsicKeyword(_) => TypeRef::Any,
50
51 TSType::TSTypeReference(type_ref) => convert_type_reference_scoped(type_ref, scope, diag),
53
54 TSType::TSArrayType(arr) => {
56 let inner = convert_ts_type_scoped(&arr.element_type, scope, diag);
57 TypeRef::Array(Box::new(inner))
58 }
59
60 TSType::TSUnionType(union_type) => {
62 let types: Vec<TypeRef> = union_type
63 .types
64 .iter()
65 .map(|t| convert_ts_type_scoped(t, scope, diag))
66 .collect();
67 simplify_union(types)
68 }
69
70 TSType::TSIntersectionType(inter) => {
72 let types: Vec<TypeRef> = inter
73 .types
74 .iter()
75 .map(|t| convert_ts_type_scoped(t, scope, diag))
76 .collect();
77 TypeRef::Intersection(types)
78 }
79
80 TSType::TSTupleType(tuple) => {
82 let types: Vec<TypeRef> = tuple
83 .element_types
84 .iter()
85 .map(|elem| convert_tuple_element_scoped(elem, scope, diag))
86 .collect();
87 TypeRef::Tuple(types)
88 }
89
90 TSType::TSFunctionType(func) => {
92 let sig = convert_function_type_scoped(func, scope, diag);
93 TypeRef::Function(sig)
94 }
95
96 TSType::TSLiteralType(lit) => convert_literal_type(lit, diag),
98
99 TSType::TSTypeLiteral(_) => TypeRef::Object,
101
102 TSType::TSConstructorType(_) => {
104 diag.warn("Constructor types are not supported, erasing to JsValue");
105 TypeRef::Any
106 }
107
108 TSType::TSConditionalType(_) => {
110 diag.warn("Conditional types are not supported, erasing to JsValue");
111 TypeRef::Unresolved("conditional type".to_string())
112 }
113 TSType::TSMappedType(_) => {
114 diag.warn("Mapped types are not supported, erasing to JsValue");
115 TypeRef::Unresolved("mapped type".to_string())
116 }
117 TSType::TSTemplateLiteralType(_) => TypeRef::String,
118 TSType::TSIndexedAccessType(_) => {
119 diag.warn("Indexed access types are not supported, erasing to JsValue");
120 TypeRef::Unresolved("indexed access type".to_string())
121 }
122 TSType::TSInferType(_) => {
123 diag.warn("Infer types are not supported, erasing to JsValue");
124 TypeRef::Unresolved("infer type".to_string())
125 }
126 TSType::TSTypeOperatorType(op) => match op.operator {
127 TSTypeOperatorOperator::Readonly => {
128 convert_ts_type_scoped(&op.type_annotation, scope, diag)
129 }
130 _ => {
131 diag.warn_with_source(
132 "Type operator not supported, erasing to JsValue",
133 format!("{:?}", op.operator),
134 );
135 TypeRef::Unresolved("type operator".to_string())
136 }
137 },
138 TSType::TSTypePredicate(_) => TypeRef::Boolean,
139 TSType::TSTypeQuery(_) => {
140 diag.warn("typeof type queries are not supported, erasing to JsValue");
141 TypeRef::Unresolved("typeof query".to_string())
142 }
143 TSType::TSImportType(_) => {
144 diag.warn("Import types are not supported, erasing to JsValue");
145 TypeRef::Unresolved("import type".to_string())
146 }
147 TSType::TSThisType(_) => TypeRef::Unresolved("this".to_string()),
148
149 TSType::TSNamedTupleMember(member) => {
151 convert_tuple_element_scoped(&member.element_type, scope, diag)
153 }
154
155 TSType::TSParenthesizedType(paren) => {
156 convert_ts_type_scoped(&paren.type_annotation, scope, diag)
157 }
158
159 TSType::JSDocNullableType(nullable) => {
161 let inner = convert_ts_type_scoped(&nullable.type_annotation, scope, diag);
162 TypeRef::Nullable(Box::new(inner))
163 }
164 TSType::JSDocNonNullableType(non_nullable) => {
165 convert_ts_type_scoped(&non_nullable.type_annotation, scope, diag)
166 }
167 TSType::JSDocUnknownType(_) => TypeRef::Any,
168 }
169}
170
171fn convert_type_reference_scoped(
173 type_ref: &TSTypeReference<'_>,
174 scope: &TypeParamScope<'_>,
175 diag: &mut DiagnosticCollector,
176) -> TypeRef {
177 let name = type_name_to_string(&type_ref.type_name);
178
179 if scope.contains(name.as_str()) {
181 return TypeRef::Any;
182 }
183
184 let type_args: Vec<TypeRef> = type_ref
186 .type_arguments
187 .as_ref()
188 .map(|args| {
189 args.params
190 .iter()
191 .map(|t| convert_ts_type_scoped(t, scope, diag))
192 .collect()
193 })
194 .unwrap_or_default();
195
196 match name.as_str() {
198 "Promise" | "PromiseLike" => {
199 let inner = type_args.into_iter().next().unwrap_or(TypeRef::Any);
200 TypeRef::Promise(Box::new(inner))
201 }
202 "Array" | "ReadonlyArray" => {
203 let inner = type_args.into_iter().next().unwrap_or(TypeRef::Any);
204 TypeRef::Array(Box::new(inner))
205 }
206 "Record" => {
207 let mut args = type_args.into_iter();
208 let key = args.next().unwrap_or(TypeRef::String);
209 let value = args.next().unwrap_or(TypeRef::Any);
210 TypeRef::Record(Box::new(key), Box::new(value))
211 }
212 "Map" | "ReadonlyMap" => {
213 let mut args = type_args.into_iter();
214 let key = args.next().unwrap_or(TypeRef::Any);
215 let value = args.next().unwrap_or(TypeRef::Any);
216 TypeRef::Map(Box::new(key), Box::new(value))
217 }
218 "Set" | "ReadonlySet" => {
219 let inner = type_args.into_iter().next().unwrap_or(TypeRef::Any);
220 TypeRef::Set(Box::new(inner))
221 }
222
223 "Int8Array" => TypeRef::Int8Array,
224 "Uint8Array" => TypeRef::Uint8Array,
225 "Uint8ClampedArray" => TypeRef::Uint8ClampedArray,
226 "Int16Array" => TypeRef::Int16Array,
227 "Uint16Array" => TypeRef::Uint16Array,
228 "Int32Array" => TypeRef::Int32Array,
229 "Uint32Array" => TypeRef::Uint32Array,
230 "Float32Array" => TypeRef::Float32Array,
231 "Float64Array" => TypeRef::Float64Array,
232 "BigInt64Array" => TypeRef::BigInt64Array,
233 "BigUint64Array" => TypeRef::BigUint64Array,
234 "ArrayBuffer" | "SharedArrayBuffer" => TypeRef::ArrayBuffer,
235 "ArrayBufferView" => TypeRef::ArrayBufferView,
236 "DataView" => TypeRef::DataView,
237 "Date" => TypeRef::Date,
238 "RegExp" => TypeRef::RegExp,
239 "Error" | "TypeError" | "RangeError" | "SyntaxError" | "DOMException" => TypeRef::Error,
240 "Boolean" => TypeRef::Boolean,
241 "Number" => TypeRef::Number,
242 "String" => TypeRef::String,
243 "Object" => TypeRef::Object,
244 "Symbol" => TypeRef::Symbol,
245 "Function" => TypeRef::Function(crate::ir::FunctionSig {
246 params: vec![],
247 return_type: Box::new(TypeRef::Any),
248 }),
249 "Partial"
250 | "Required"
251 | "Pick"
252 | "Omit"
253 | "Exclude"
254 | "Extract"
255 | "NonNullable"
256 | "ReturnType"
257 | "Parameters"
258 | "ConstructorParameters"
259 | "InstanceType"
260 | "ThisParameterType"
261 | "OmitThisParameter"
262 | "ThisType"
263 | "Awaited" => type_args.into_iter().next().unwrap_or(TypeRef::Object),
264 _ => {
265 if type_args.is_empty() {
266 TypeRef::Named(name)
267 } else {
268 TypeRef::GenericInstantiation(name, type_args)
269 }
270 }
271 }
272}
273
274fn type_name_to_string(type_name: &TSTypeName<'_>) -> String {
276 match type_name {
277 TSTypeName::IdentifierReference(ident) => ident.name.to_string(),
278 TSTypeName::QualifiedName(qualified) => {
279 let left = type_name_to_string(&qualified.left);
280 let right = &qualified.right.name;
281 format!("{left}.{right}")
282 }
283 TSTypeName::ThisExpression(_) => "this".to_string(),
284 }
285}
286
287fn convert_tuple_element_scoped(
289 elem: &TSTupleElement<'_>,
290 scope: &TypeParamScope<'_>,
291 diag: &mut DiagnosticCollector,
292) -> TypeRef {
293 match elem {
294 TSTupleElement::TSNamedTupleMember(member) => {
295 convert_tuple_element_scoped(&member.element_type, scope, diag)
296 }
297 TSTupleElement::TSRestType(rest) => {
298 convert_ts_type_scoped(&rest.type_annotation, scope, diag)
299 }
300 TSTupleElement::TSOptionalType(opt) => {
301 let inner = convert_ts_type_scoped(&opt.type_annotation, scope, diag);
302 TypeRef::Nullable(Box::new(inner))
303 }
304 other => {
306 if let Some(ts_type) = tuple_element_as_ts_type(other) {
307 convert_ts_type_scoped(ts_type, scope, diag)
308 } else {
309 diag.warn("Unsupported tuple element type");
310 TypeRef::Any
311 }
312 }
313 }
314}
315
316fn tuple_element_as_ts_type<'a>(elem: &'a TSTupleElement<'a>) -> Option<&'a TSType<'a>> {
320 elem.as_ts_type()
321}
322
323fn convert_function_type_scoped(
325 func: &TSFunctionType<'_>,
326 scope: &TypeParamScope<'_>,
327 diag: &mut DiagnosticCollector,
328) -> crate::ir::FunctionSig {
329 let mut inner_scope = scope.clone();
331 if let Some(tp) = &func.type_parameters {
332 for p in &tp.params {
333 inner_scope.insert(p.name.name.as_str());
334 }
335 }
336
337 let params = convert_formal_params(&func.params, diag);
338 let return_type = convert_ts_type_scoped(&func.return_type.type_annotation, &inner_scope, diag);
339
340 crate::ir::FunctionSig {
341 params,
342 return_type: Box::new(return_type),
343 }
344}
345
346pub fn convert_formal_params(
348 params: &FormalParameters<'_>,
349 diag: &mut DiagnosticCollector,
350) -> Vec<crate::ir::Param> {
351 let mut result = Vec::new();
352 for (i, param) in params.items.iter().enumerate() {
353 let name = binding_pattern_name(¶m.pattern)
354 .map(|n| to_snake_case(&n))
355 .unwrap_or_else(|| format!("arg{i}"));
356
357 let type_ref = param
359 .type_annotation
360 .as_ref()
361 .map(|ann| convert_ts_type(&ann.type_annotation, diag))
362 .unwrap_or(TypeRef::Any);
363
364 let optional = param.optional;
365
366 result.push(crate::ir::Param {
367 name,
368 type_ref,
369 optional,
370 variadic: false,
371 });
372 }
373
374 if let Some(rest) = ¶ms.rest {
376 let name = binding_pattern_name(&rest.rest.argument).unwrap_or_else(|| "rest".to_string());
377
378 let type_ref = rest
379 .type_annotation
380 .as_ref()
381 .map(|ann| convert_ts_type(&ann.type_annotation, diag))
382 .unwrap_or(TypeRef::Array(Box::new(TypeRef::Any)));
383
384 result.push(crate::ir::Param {
385 name,
386 type_ref,
387 optional: false,
388 variadic: true,
389 });
390 }
391
392 result
393}
394
395fn binding_pattern_name(pattern: &BindingPattern<'_>) -> Option<String> {
398 match pattern {
399 BindingPattern::BindingIdentifier(ident) => Some(ident.name.to_string()),
400 _ => None,
401 }
402}
403
404fn convert_literal_type(lit: &TSLiteralType<'_>, _diag: &mut DiagnosticCollector) -> TypeRef {
406 match &lit.literal {
407 TSLiteral::BooleanLiteral(b) => TypeRef::BooleanLiteral(b.value),
408 TSLiteral::NumericLiteral(n) => TypeRef::NumberLiteral(n.value),
409 TSLiteral::StringLiteral(s) => TypeRef::StringLiteral(s.value.to_string()),
410 TSLiteral::BigIntLiteral(_) => TypeRef::BigInt,
411 TSLiteral::TemplateLiteral(_) => TypeRef::String,
412 TSLiteral::UnaryExpression(_) => TypeRef::Number,
413 }
414}
415
416fn simplify_union(types: Vec<TypeRef>) -> TypeRef {
418 let mut non_null_types = Vec::new();
419 let mut has_null = false;
420 let mut has_undefined = false;
421
422 for ty in types {
423 match ty {
424 TypeRef::Null => has_null = true,
425 TypeRef::Undefined => has_undefined = true,
426 TypeRef::Void => has_undefined = true,
427 other => non_null_types.push(other),
428 }
429 }
430
431 let core_type = if non_null_types.len() == 1 {
432 non_null_types.pop().unwrap()
433 } else if non_null_types.is_empty() {
434 return TypeRef::Null;
435 } else {
436 TypeRef::Union(non_null_types)
437 };
438
439 if has_null || has_undefined {
440 TypeRef::Nullable(Box::new(core_type))
441 } else {
442 core_type
443 }
444}
445
446pub fn convert_type_params(
448 type_params: Option<&oxc_allocator::Box<'_, TSTypeParameterDeclaration<'_>>>,
449 diag: &mut DiagnosticCollector,
450) -> Vec<crate::ir::TypeParam> {
451 type_params
452 .map(|tp| {
453 tp.params
454 .iter()
455 .map(|p| crate::ir::TypeParam {
456 name: p.name.to_string(),
457 constraint: p.constraint.as_ref().map(|c| convert_ts_type(c, diag)),
458 default: p.default.as_ref().map(|d| convert_ts_type(d, diag)),
459 })
460 .collect()
461 })
462 .unwrap_or_default()
463}