mirror of
https://github.com/danbulant/oxc
synced 2026-05-22 21:58:36 +00:00
refactor(transformer/typescript): reimplementation of Enum conversion based on Babel (#3102)
The remaining test cases will perform better with a scope implementation, and while we can implement them without the scope, it still requires us to do what the scope did. --------- Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
parent
905ee3fef1
commit
843318cdbe
15 changed files with 608 additions and 277 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1655,6 +1655,7 @@ dependencies = [
|
|||
"oxc_span",
|
||||
"oxc_syntax",
|
||||
"rustc-hash",
|
||||
"ryu-js",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ oxc_syntax = { workspace = true }
|
|||
rustc-hash = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
ryu-js = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
oxc_parser = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ mod options;
|
|||
mod es2015;
|
||||
mod react;
|
||||
mod typescript;
|
||||
mod utils;
|
||||
|
||||
mod helpers {
|
||||
pub mod module_imports;
|
||||
|
|
@ -223,6 +222,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
|||
}
|
||||
|
||||
fn visit_statement(&mut self, stmt: &mut Statement<'a>) {
|
||||
self.x0_typescript.transform_statement(stmt);
|
||||
walk_mut::walk_statement_mut(self, stmt);
|
||||
}
|
||||
|
||||
|
|
|
|||
83
crates/oxc_transformer/src/typescript/conversions.rs
Normal file
83
crates/oxc_transformer/src/typescript/conversions.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/// This file copied from [Boa](https://github.com/boa-dev/boa/blob/61567687cf4bfeca6bd548c3e72b6965e74b2461/core/engine/src/builtins/number/conversions.rs)
|
||||
|
||||
/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm.
|
||||
///
|
||||
/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32
|
||||
#[allow(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
pub(crate) fn f64_to_int32(number: f64) -> i32 {
|
||||
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
|
||||
const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000;
|
||||
const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF;
|
||||
const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000;
|
||||
const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit.
|
||||
const SIGNIFICAND_SIZE: i32 = 53;
|
||||
|
||||
const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE;
|
||||
const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1;
|
||||
|
||||
fn is_denormal(number: f64) -> bool {
|
||||
(number.to_bits() & EXPONENT_MASK) == 0
|
||||
}
|
||||
|
||||
fn exponent(number: f64) -> i32 {
|
||||
if is_denormal(number) {
|
||||
return DENORMAL_EXPONENT;
|
||||
}
|
||||
|
||||
let d64 = number.to_bits();
|
||||
let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32;
|
||||
|
||||
biased_e - EXPONENT_BIAS
|
||||
}
|
||||
|
||||
fn significand(number: f64) -> u64 {
|
||||
let d64 = number.to_bits();
|
||||
let significand = d64 & SIGNIFICAND_MASK;
|
||||
|
||||
if is_denormal(number) {
|
||||
significand
|
||||
} else {
|
||||
significand + HIDDEN_BIT
|
||||
}
|
||||
}
|
||||
|
||||
fn sign(number: f64) -> i64 {
|
||||
if (number.to_bits() & SIGN_MASK) == 0 {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) {
|
||||
let i = number as i32;
|
||||
if f64::from(i) == number {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
let exponent = exponent(number);
|
||||
let bits = if exponent < 0 {
|
||||
if exponent <= -SIGNIFICAND_SIZE {
|
||||
return 0;
|
||||
}
|
||||
|
||||
significand(number) >> -exponent
|
||||
} else {
|
||||
if exponent > 31 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(significand(number) << exponent) & 0xFFFF_FFFF
|
||||
};
|
||||
|
||||
(sign(number) * (bits as i64)) as i32
|
||||
}
|
||||
|
||||
/// Converts a 64-bit floating point number to an `u32` according to the [`ToUint32`][ToUint32] algorithm.
|
||||
///
|
||||
/// [ToUint32]: https://tc39.es/ecma262/#sec-touint32
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
pub(crate) fn f64_to_uint32(number: f64) -> u32 {
|
||||
f64_to_int32(number) as u32
|
||||
}
|
||||
|
|
@ -1,33 +1,46 @@
|
|||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use oxc_allocator::{Box, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast::{ast::*, visit::walk_mut, VisitMut};
|
||||
use oxc_span::{Atom, SPAN};
|
||||
use oxc_syntax::{
|
||||
operator::{AssignmentOperator, BinaryOperator, LogicalOperator},
|
||||
operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator},
|
||||
NumberBase,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use ryu_js::Buffer;
|
||||
|
||||
use crate::utils::is_valid_identifier;
|
||||
use crate::context::Ctx;
|
||||
|
||||
use super::TypeScript;
|
||||
use super::conversions::{f64_to_int32, f64_to_uint32};
|
||||
|
||||
impl<'a> TypeScript<'a> {
|
||||
pub struct TypeScriptEnum<'a> {
|
||||
ctx: Ctx<'a>,
|
||||
enums: FxHashMap<Atom<'a>, FxHashMap<Atom<'a>, ConstantValue>>,
|
||||
}
|
||||
|
||||
impl<'a> TypeScriptEnum<'a> {
|
||||
pub fn new(ctx: &Ctx<'a>) -> Self {
|
||||
Self { ctx: Rc::clone(ctx), enums: FxHashMap::default() }
|
||||
}
|
||||
/// ```TypeScript
|
||||
/// enum Foo {
|
||||
/// X
|
||||
/// X = 1,
|
||||
/// Y
|
||||
/// }
|
||||
/// ```
|
||||
/// ```JavaScript
|
||||
/// var Foo = ((Foo) => {
|
||||
/// const X = 0; Foo[Foo["X"] = X] = "X";
|
||||
/// Foo[Foo["X"] = 1] = "X";
|
||||
/// Foo[Foo["Y"] = 2] = "Y";
|
||||
/// return Foo;
|
||||
/// })(Foo || {});
|
||||
/// ```
|
||||
pub fn transform_ts_enum(
|
||||
&self,
|
||||
decl: &mut Box<'a, TSEnumDeclaration<'a>>,
|
||||
) -> Option<Declaration<'a>> {
|
||||
&mut self,
|
||||
decl: &Box<'a, TSEnumDeclaration<'a>>,
|
||||
is_export: bool,
|
||||
) -> Option<Statement<'a>> {
|
||||
if decl.modifiers.contains(ModifierKind::Declare) {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -37,18 +50,10 @@ impl<'a> TypeScript<'a> {
|
|||
let kind = self.ctx.ast.binding_pattern_identifier(ident);
|
||||
let id = self.ctx.ast.binding_pattern(kind, None, false);
|
||||
|
||||
let mut params = self.ctx.ast.new_vec();
|
||||
|
||||
// ((Foo) => {
|
||||
params.push(self.ctx.ast.formal_parameter(
|
||||
SPAN,
|
||||
id,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
self.ctx.ast.new_vec(),
|
||||
));
|
||||
|
||||
let params =
|
||||
self.ctx.ast.formal_parameter(SPAN, id, None, false, false, self.ctx.ast.new_vec());
|
||||
let params = self.ctx.ast.new_vec_single(params);
|
||||
let params = self.ctx.ast.formal_parameters(
|
||||
SPAN,
|
||||
FormalParameterKind::ArrowFormalParameters,
|
||||
|
|
@ -58,26 +63,43 @@ impl<'a> TypeScript<'a> {
|
|||
|
||||
// Foo[Foo["X"] = 0] = "X";
|
||||
let enum_name = decl.id.name.clone();
|
||||
let statements = self.transform_ts_enum_members(&mut decl.members, &enum_name);
|
||||
let is_already_declared = self.enums.contains_key(&enum_name);
|
||||
let statements = self.transform_ts_enum_members(&decl.members, &enum_name);
|
||||
let body = self.ctx.ast.function_body(decl.span, self.ctx.ast.new_vec(), statements);
|
||||
let r#type = FunctionType::FunctionExpression;
|
||||
let callee = self.ctx.ast.plain_function(r#type, SPAN, None, params, Some(body));
|
||||
let callee = Expression::FunctionExpression(callee);
|
||||
|
||||
let callee =
|
||||
self.ctx.ast.arrow_function_expression(SPAN, false, false, params, body, None, None);
|
||||
|
||||
// })(Foo || {});
|
||||
let mut arguments = self.ctx.ast.new_vec();
|
||||
let op = LogicalOperator::Or;
|
||||
let left = self
|
||||
.ctx
|
||||
.ast
|
||||
.identifier_reference_expression(IdentifierReference::new(SPAN, enum_name.clone()));
|
||||
let right = self.ctx.ast.object_expression(SPAN, self.ctx.ast.new_vec(), None);
|
||||
let expression = self.ctx.ast.logical_expression(SPAN, left, op, right);
|
||||
arguments.push(Argument::from(expression));
|
||||
let arguments = if is_export && !is_already_declared {
|
||||
// }({});
|
||||
let object_expr = self.ctx.ast.object_expression(SPAN, self.ctx.ast.new_vec(), None);
|
||||
self.ctx.ast.new_vec_single(Argument::from(object_expr))
|
||||
} else {
|
||||
// }(Foo || {});
|
||||
let op = LogicalOperator::Or;
|
||||
let left = self
|
||||
.ctx
|
||||
.ast
|
||||
.identifier_reference_expression(IdentifierReference::new(SPAN, enum_name.clone()));
|
||||
let right = self.ctx.ast.object_expression(SPAN, self.ctx.ast.new_vec(), None);
|
||||
let expression = self.ctx.ast.logical_expression(SPAN, left, op, right);
|
||||
self.ctx.ast.new_vec_single(Argument::from(expression))
|
||||
};
|
||||
|
||||
let call_expression = self.ctx.ast.call_expression(SPAN, callee, arguments, false, None);
|
||||
|
||||
let kind = VariableDeclarationKind::Var;
|
||||
if is_already_declared {
|
||||
let op = AssignmentOperator::Assign;
|
||||
let left = self.ctx.ast.simple_assignment_target_identifier(IdentifierReference::new(
|
||||
SPAN,
|
||||
enum_name.clone(),
|
||||
));
|
||||
let expr = self.ctx.ast.assignment_expression(SPAN, op, left, call_expression);
|
||||
return Some(self.ctx.ast.expression_statement(SPAN, expr));
|
||||
}
|
||||
|
||||
let kind =
|
||||
if is_export { VariableDeclarationKind::Let } else { VariableDeclarationKind::Var };
|
||||
let decls = {
|
||||
let mut decls = self.ctx.ast.new_vec();
|
||||
|
||||
|
|
@ -92,78 +114,106 @@ impl<'a> TypeScript<'a> {
|
|||
};
|
||||
let variable_declaration =
|
||||
self.ctx.ast.variable_declaration(span, kind, decls, Modifiers::empty());
|
||||
let variable_declaration = Declaration::VariableDeclaration(variable_declaration);
|
||||
|
||||
Some(Declaration::VariableDeclaration(variable_declaration))
|
||||
let stmt = if is_export {
|
||||
let declaration =
|
||||
self.ctx.ast.plain_export_named_declaration_declaration(SPAN, variable_declaration);
|
||||
|
||||
self.ctx.ast.module_declaration(ModuleDeclaration::ExportNamedDeclaration(declaration))
|
||||
} else {
|
||||
Statement::from(variable_declaration)
|
||||
};
|
||||
Some(stmt)
|
||||
}
|
||||
|
||||
pub fn transform_ts_enum_members(
|
||||
&self,
|
||||
members: &mut Vec<'a, TSEnumMember<'a>>,
|
||||
&mut self,
|
||||
members: &Vec<'a, TSEnumMember<'a>>,
|
||||
enum_name: &Atom<'a>,
|
||||
) -> Vec<'a, Statement<'a>> {
|
||||
let mut default_init = self.ctx.ast.literal_number_expression(NumericLiteral {
|
||||
span: SPAN,
|
||||
value: 0.0,
|
||||
raw: "0",
|
||||
base: NumberBase::Decimal,
|
||||
});
|
||||
let mut statements = self.ctx.ast.new_vec();
|
||||
let mut prev_constant_value = Some(ConstantValue::Number(-1.0));
|
||||
let mut previous_enum_members = self.enums.entry(enum_name.clone()).or_default().clone();
|
||||
let mut prev_member_name: Option<Atom<'a>> = None;
|
||||
|
||||
for member in members.iter_mut() {
|
||||
let (member_name, member_span) = match &member.id {
|
||||
TSEnumMemberName::StaticIdentifier(id) => (&id.name, id.span),
|
||||
TSEnumMemberName::StaticStringLiteral(str) => (&str.value, str.span),
|
||||
for member in members {
|
||||
let member_name = match &member.id {
|
||||
TSEnumMemberName::StaticIdentifier(id) => &id.name,
|
||||
TSEnumMemberName::StaticStringLiteral(str) => &str.value,
|
||||
#[allow(clippy::unnested_or_patterns)] // Clippy is wrong
|
||||
TSEnumMemberName::StaticNumericLiteral(_) | match_expression!(TSEnumMemberName) => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let mut init = self
|
||||
.ctx
|
||||
.ast
|
||||
.move_expression(member.initializer.as_mut().unwrap_or(&mut default_init));
|
||||
let init = if let Some(initializer) = member.initializer.as_ref() {
|
||||
let constant_value =
|
||||
self.computed_constant_value(initializer, &previous_enum_members);
|
||||
|
||||
let is_str = init.is_string_literal();
|
||||
// prev_constant_value = constant_value
|
||||
let init = match constant_value {
|
||||
None => {
|
||||
prev_constant_value = None;
|
||||
let mut new_initializer = self.ctx.ast.copy(initializer);
|
||||
IdentifierReferenceRename::new(
|
||||
enum_name.clone(),
|
||||
previous_enum_members.clone(),
|
||||
&self.ctx,
|
||||
)
|
||||
.visit_expression(&mut new_initializer);
|
||||
new_initializer
|
||||
}
|
||||
Some(constant_value) => {
|
||||
previous_enum_members.insert(member_name.clone(), constant_value.clone());
|
||||
match constant_value {
|
||||
ConstantValue::Number(v) => {
|
||||
prev_constant_value = Some(ConstantValue::Number(v));
|
||||
self.get_initializer_expr(v)
|
||||
}
|
||||
ConstantValue::String(str) => {
|
||||
prev_constant_value = None;
|
||||
self.ctx.ast.literal_string_expression(StringLiteral {
|
||||
span: SPAN,
|
||||
value: self.ctx.ast.new_atom(&str),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut self_ref = {
|
||||
let obj = self.ctx.ast.identifier_reference_expression(IdentifierReference::new(
|
||||
SPAN,
|
||||
enum_name.clone(),
|
||||
));
|
||||
let expr = self
|
||||
.ctx
|
||||
.ast
|
||||
.literal_string_expression(StringLiteral::new(SPAN, member_name.clone()));
|
||||
self.ctx.ast.computed_member_expression(SPAN, obj, expr, false)
|
||||
init
|
||||
} else if let Some(ref value) = prev_constant_value {
|
||||
match value {
|
||||
ConstantValue::Number(value) => {
|
||||
let value = value + 1.0;
|
||||
let constant_value = ConstantValue::Number(value);
|
||||
prev_constant_value = Some(constant_value.clone());
|
||||
previous_enum_members.insert(member_name.clone(), constant_value);
|
||||
self.get_initializer_expr(value)
|
||||
}
|
||||
ConstantValue::String(_) => unreachable!(),
|
||||
}
|
||||
} else if let Some(prev_member_name) = prev_member_name {
|
||||
let self_ref = {
|
||||
let obj = self.ctx.ast.identifier_reference_expression(
|
||||
IdentifierReference::new(SPAN, enum_name.clone()),
|
||||
);
|
||||
let expr = self
|
||||
.ctx
|
||||
.ast
|
||||
.literal_string_expression(StringLiteral::new(SPAN, prev_member_name));
|
||||
self.ctx.ast.computed_member_expression(SPAN, obj, expr, false)
|
||||
};
|
||||
|
||||
// 1 + Foo["x"]
|
||||
let one = self.get_number_literal_expression(1.0);
|
||||
self.ctx.ast.binary_expression(SPAN, one, BinaryOperator::Addition, self_ref)
|
||||
} else {
|
||||
self.get_number_literal_expression(0.0)
|
||||
};
|
||||
|
||||
if is_valid_identifier(member_name, true) {
|
||||
let ident = IdentifierReference::new(member_span, member_name.clone());
|
||||
|
||||
self_ref = self.ctx.ast.identifier_reference_expression(ident.clone());
|
||||
let init =
|
||||
mem::replace(&mut init, self.ctx.ast.identifier_reference_expression(ident));
|
||||
|
||||
let kind = VariableDeclarationKind::Const;
|
||||
let decls = {
|
||||
let mut decls = self.ctx.ast.new_vec();
|
||||
|
||||
let binding_identifier = BindingIdentifier::new(SPAN, member_name.clone());
|
||||
let binding_pattern_kind =
|
||||
self.ctx.ast.binding_pattern_identifier(binding_identifier);
|
||||
let binding = self.ctx.ast.binding_pattern(binding_pattern_kind, None, false);
|
||||
let decl =
|
||||
self.ctx.ast.variable_declarator(SPAN, kind, binding, Some(init), false);
|
||||
|
||||
decls.push(decl);
|
||||
decls
|
||||
};
|
||||
let decl = self.ctx.ast.variable_declaration(SPAN, kind, decls, Modifiers::empty());
|
||||
let stmt: Statement<'_> = Statement::VariableDeclaration(decl);
|
||||
|
||||
statements.push(stmt);
|
||||
}
|
||||
let is_str = init.is_string_literal();
|
||||
|
||||
// Foo["x"] = init
|
||||
let member_expr = {
|
||||
|
|
@ -171,10 +221,8 @@ impl<'a> TypeScript<'a> {
|
|||
SPAN,
|
||||
enum_name.clone(),
|
||||
));
|
||||
let expr = self
|
||||
.ctx
|
||||
.ast
|
||||
.literal_string_expression(StringLiteral::new(SPAN, member_name.clone()));
|
||||
let literal = StringLiteral::new(SPAN, member_name.clone());
|
||||
let expr = self.ctx.ast.literal_string_expression(literal);
|
||||
|
||||
self.ctx.ast.computed_member(SPAN, obj, expr, false)
|
||||
};
|
||||
|
|
@ -203,21 +251,12 @@ impl<'a> TypeScript<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
prev_member_name = Some(member_name.clone());
|
||||
statements.push(self.ctx.ast.expression_statement(member.span, expr));
|
||||
|
||||
// 1 + Foo["x"]
|
||||
default_init = {
|
||||
let one = self.ctx.ast.literal_number_expression(NumericLiteral {
|
||||
span: SPAN,
|
||||
value: 1.0,
|
||||
raw: "1",
|
||||
base: NumberBase::Decimal,
|
||||
});
|
||||
|
||||
self.ctx.ast.binary_expression(SPAN, one, BinaryOperator::Addition, self_ref)
|
||||
};
|
||||
}
|
||||
|
||||
self.enums.insert(enum_name.clone(), previous_enum_members.clone());
|
||||
|
||||
let enum_ref = self
|
||||
.ctx
|
||||
.ast
|
||||
|
|
@ -228,4 +267,278 @@ impl<'a> TypeScript<'a> {
|
|||
|
||||
statements
|
||||
}
|
||||
|
||||
fn get_number_literal_expression(&self, value: f64) -> Expression<'a> {
|
||||
self.ctx.ast.literal_number_expression(NumericLiteral {
|
||||
span: SPAN,
|
||||
value,
|
||||
raw: self.ctx.ast.new_str(&value.to_string()),
|
||||
base: NumberBase::Decimal,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_initializer_expr(&self, value: f64) -> Expression<'a> {
|
||||
let is_negative = value < 0.0;
|
||||
|
||||
// Infinity
|
||||
let expr = if value.is_infinite() {
|
||||
let ident = IdentifierReference::new(SPAN, self.ctx.ast.new_atom("Infinity"));
|
||||
self.ctx.ast.identifier_reference_expression(ident)
|
||||
} else {
|
||||
let value = if is_negative { -value } else { value };
|
||||
self.get_number_literal_expression(value)
|
||||
};
|
||||
|
||||
if is_negative {
|
||||
self.ctx.ast.unary_expression(SPAN, UnaryOperator::UnaryNegation, expr)
|
||||
} else {
|
||||
expr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ConstantValue {
|
||||
Number(f64),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl<'a> TypeScriptEnum<'a> {
|
||||
/// Evaluate the expression to a constant value.
|
||||
/// Refer to [babel](https://github.com/babel/babel/blob/610897a9a96c5e344e77ca9665df7613d2f88358/packages/babel-plugin-transform-typescript/src/enum.ts#L241C1-L394C2)
|
||||
fn computed_constant_value(
|
||||
&self,
|
||||
expr: &Expression<'a>,
|
||||
prev_members: &FxHashMap<Atom<'a>, ConstantValue>,
|
||||
) -> Option<ConstantValue> {
|
||||
self.evaluate(expr, prev_members)
|
||||
}
|
||||
|
||||
fn evalaute_ref(
|
||||
&self,
|
||||
expr: &Expression<'a>,
|
||||
prev_members: &FxHashMap<Atom<'a>, ConstantValue>,
|
||||
) -> Option<ConstantValue> {
|
||||
match expr {
|
||||
match_member_expression!(Expression) => {
|
||||
let expr = expr.to_member_expression();
|
||||
let Expression::Identifier(ident) = expr.object() else { return None };
|
||||
let members = self.enums.get(&ident.name)?;
|
||||
let property = expr.static_property_name()?;
|
||||
return members.get(property).cloned();
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if ident.name == "Infinity" {
|
||||
return Some(ConstantValue::Number(f64::INFINITY));
|
||||
} else if ident.name == "NaN" {
|
||||
return Some(ConstantValue::Number(f64::NAN));
|
||||
}
|
||||
|
||||
if let Some(value) = prev_members.get(&ident.name) {
|
||||
return Some(value.clone());
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// This is a bit tricky because we need to find the BindingIdentifier that corresponds to the identifier reference.
|
||||
// and then we may to evaluate the initializer of the BindingIdentifier.
|
||||
// finally, we can get the value of the identifier and call the `computed_constant_value` function.
|
||||
// See https://github.com/babel/babel/blob/610897a9a96c5e344e77ca9665df7613d2f88358/packages/babel-plugin-transform-typescript/src/enum.ts#L327-L329
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate(
|
||||
&self,
|
||||
expr: &Expression<'a>,
|
||||
prev_members: &FxHashMap<Atom<'a>, ConstantValue>,
|
||||
) -> Option<ConstantValue> {
|
||||
match expr {
|
||||
Expression::Identifier(_)
|
||||
| Expression::ComputedMemberExpression(_)
|
||||
| Expression::StaticMemberExpression(_)
|
||||
| Expression::PrivateFieldExpression(_) => self.evalaute_ref(expr, prev_members),
|
||||
Expression::BinaryExpression(expr) => self.eval_binary_expression(expr, prev_members),
|
||||
Expression::UnaryExpression(expr) => self.eval_unary_expression(expr, prev_members),
|
||||
Expression::NumericLiteral(lit) => Some(ConstantValue::Number(lit.value)),
|
||||
Expression::StringLiteral(lit) => Some(ConstantValue::String(lit.value.to_string())),
|
||||
Expression::TemplateLiteral(lit) => {
|
||||
let mut value = String::new();
|
||||
for part in &lit.quasis {
|
||||
value.push_str(&part.value.raw);
|
||||
}
|
||||
Some(ConstantValue::String(value))
|
||||
}
|
||||
Expression::ParenthesizedExpression(expr) => {
|
||||
self.evaluate(&expr.expression, prev_members)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
||||
fn eval_binary_expression(
|
||||
&self,
|
||||
expr: &BinaryExpression<'a>,
|
||||
prev_members: &FxHashMap<Atom<'a>, ConstantValue>,
|
||||
) -> Option<ConstantValue> {
|
||||
let left = self.evaluate(&expr.left, prev_members)?;
|
||||
let right = self.evaluate(&expr.right, prev_members)?;
|
||||
|
||||
if matches!(expr.operator, BinaryOperator::Addition)
|
||||
&& (matches!(left, ConstantValue::String(_))
|
||||
|| matches!(right, ConstantValue::String(_)))
|
||||
{
|
||||
let left_string = match left {
|
||||
ConstantValue::String(str) => str,
|
||||
ConstantValue::Number(v) => f64_to_js_string(v),
|
||||
};
|
||||
|
||||
let right_string = match right {
|
||||
ConstantValue::String(str) => str,
|
||||
ConstantValue::Number(v) => f64_to_js_string(v),
|
||||
};
|
||||
|
||||
return Some(ConstantValue::String(format!("{left_string}{right_string}")));
|
||||
}
|
||||
|
||||
let left = match left {
|
||||
ConstantValue::Number(v) => v,
|
||||
ConstantValue::String(_) => return None,
|
||||
};
|
||||
|
||||
let right = match right {
|
||||
ConstantValue::Number(v) => v,
|
||||
ConstantValue::String(_) => return None,
|
||||
};
|
||||
|
||||
match expr.operator {
|
||||
BinaryOperator::ShiftRight => Some(ConstantValue::Number(f64::from(
|
||||
f64_to_int32(left).wrapping_shr(f64_to_uint32(right)),
|
||||
))),
|
||||
BinaryOperator::ShiftRightZeroFill => Some(ConstantValue::Number(f64::from(
|
||||
f64_to_uint32(left).wrapping_shr(f64_to_uint32(right)),
|
||||
))),
|
||||
BinaryOperator::ShiftLeft => Some(ConstantValue::Number(f64::from(
|
||||
f64_to_int32(left).wrapping_shl(f64_to_uint32(right)),
|
||||
))),
|
||||
BinaryOperator::BitwiseXOR => {
|
||||
Some(ConstantValue::Number(f64::from(f64_to_int32(left) ^ f64_to_int32(right))))
|
||||
}
|
||||
BinaryOperator::BitwiseOR => {
|
||||
Some(ConstantValue::Number(f64::from(f64_to_int32(left) | f64_to_int32(right))))
|
||||
}
|
||||
BinaryOperator::BitwiseAnd => {
|
||||
Some(ConstantValue::Number(f64::from(f64_to_int32(left) & f64_to_int32(right))))
|
||||
}
|
||||
BinaryOperator::Multiplication => Some(ConstantValue::Number(left * right)),
|
||||
BinaryOperator::Division => Some(ConstantValue::Number(left / right)),
|
||||
BinaryOperator::Addition => Some(ConstantValue::Number(left + right)),
|
||||
BinaryOperator::Subtraction => Some(ConstantValue::Number(left - right)),
|
||||
BinaryOperator::Remainder => Some(ConstantValue::Number(left % right)),
|
||||
BinaryOperator::Exponential => Some(ConstantValue::Number(left.powf(right))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
||||
fn eval_unary_expression(
|
||||
&self,
|
||||
expr: &UnaryExpression<'a>,
|
||||
prev_members: &FxHashMap<Atom<'a>, ConstantValue>,
|
||||
) -> Option<ConstantValue> {
|
||||
let value = self.evaluate(&expr.argument, prev_members)?;
|
||||
|
||||
let value = match value {
|
||||
ConstantValue::Number(value) => value,
|
||||
ConstantValue::String(_) => {
|
||||
let value = if expr.operator == UnaryOperator::UnaryNegation {
|
||||
ConstantValue::Number(f64::NAN)
|
||||
} else if expr.operator == UnaryOperator::BitwiseNot {
|
||||
ConstantValue::Number(-1.0)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
return Some(value);
|
||||
}
|
||||
};
|
||||
|
||||
match expr.operator {
|
||||
UnaryOperator::UnaryPlus => Some(ConstantValue::Number(value)),
|
||||
UnaryOperator::UnaryNegation => Some(ConstantValue::Number(-value)),
|
||||
UnaryOperator::BitwiseNot => {
|
||||
Some(ConstantValue::Number(f64::from(!f64_to_int32(value))))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rename the identifier references in the enum members to `enum_name.identifier`
|
||||
/// ```ts
|
||||
/// enum A {
|
||||
/// a = 1,
|
||||
/// b = a.toString(),
|
||||
/// d = c,
|
||||
/// }
|
||||
/// ```
|
||||
/// will be transformed to
|
||||
/// ```ts
|
||||
/// enum A {
|
||||
/// a = 1,
|
||||
/// b = A.a.toString(),
|
||||
/// d = A.c,
|
||||
/// }
|
||||
/// ```
|
||||
struct IdentifierReferenceRename<'a> {
|
||||
enum_name: Atom<'a>,
|
||||
ctx: Ctx<'a>,
|
||||
previous_enum_members: FxHashMap<Atom<'a>, ConstantValue>,
|
||||
}
|
||||
|
||||
impl IdentifierReferenceRename<'_> {
|
||||
fn new<'a>(
|
||||
enum_name: Atom<'a>,
|
||||
previous_enum_members: FxHashMap<Atom<'a>, ConstantValue>,
|
||||
ctx: &Ctx<'a>,
|
||||
) -> IdentifierReferenceRename<'a> {
|
||||
IdentifierReferenceRename { enum_name, ctx: Rc::clone(ctx), previous_enum_members }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VisitMut<'a> for IdentifierReferenceRename<'a> {
|
||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
||||
let new_expr = match expr {
|
||||
match_member_expression!(Expression) => {
|
||||
// handle a.toString() -> A.a.toString()
|
||||
let expr = expr.to_member_expression();
|
||||
if let Expression::Identifier(ident) = expr.object() {
|
||||
if !self.previous_enum_members.contains_key(&ident.name) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
None
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
// TODO: shadowed case, e.g. let ident = 1; ident; // ident is not an enum
|
||||
// enum_name.identifier
|
||||
let ident_reference = IdentifierReference::new(SPAN, self.enum_name.clone());
|
||||
let object = self.ctx.ast.identifier_reference_expression(ident_reference);
|
||||
let property = self.ctx.ast.identifier_name(SPAN, &ident.name);
|
||||
Some(self.ctx.ast.static_member_expression(SPAN, object, property, false))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(new_expr) = new_expr {
|
||||
*expr = new_expr;
|
||||
} else {
|
||||
walk_mut::walk_expression_mut(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn f64_to_js_string(value: f64) -> String {
|
||||
let mut buffer = Buffer::new();
|
||||
buffer.format(value).to_string()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mod annotations;
|
||||
mod collector;
|
||||
mod conversions;
|
||||
mod diagnostics;
|
||||
mod r#enum;
|
||||
mod module;
|
||||
|
|
@ -14,7 +15,10 @@ use oxc_ast::ast::*;
|
|||
|
||||
use crate::context::Ctx;
|
||||
|
||||
use self::{annotations::TypeScriptAnnotations, collector::TypeScriptReferenceCollector};
|
||||
use self::{
|
||||
annotations::TypeScriptAnnotations, collector::TypeScriptReferenceCollector,
|
||||
r#enum::TypeScriptEnum,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
|
|
@ -51,6 +55,7 @@ pub struct TypeScript<'a> {
|
|||
ctx: Ctx<'a>,
|
||||
|
||||
annotations: TypeScriptAnnotations<'a>,
|
||||
r#enum: TypeScriptEnum<'a>,
|
||||
reference_collector: TypeScriptReferenceCollector<'a>,
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +65,7 @@ impl<'a> TypeScript<'a> {
|
|||
|
||||
Self {
|
||||
annotations: TypeScriptAnnotations::new(&options, ctx),
|
||||
r#enum: TypeScriptEnum::new(ctx),
|
||||
reference_collector: TypeScriptReferenceCollector::new(),
|
||||
options,
|
||||
ctx: Rc::clone(ctx),
|
||||
|
|
@ -141,6 +147,36 @@ impl<'a> TypeScript<'a> {
|
|||
self.annotations.transform_statements_on_exit(stmts);
|
||||
}
|
||||
|
||||
pub fn transform_statement(&mut self, stmt: &mut Statement<'a>) {
|
||||
let new_stmt = match stmt {
|
||||
match_declaration!(Statement) => {
|
||||
if let Declaration::TSEnumDeclaration(ts_enum_decl) = &stmt.to_declaration() {
|
||||
self.r#enum.transform_ts_enum(ts_enum_decl, false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
match_module_declaration!(Statement) => {
|
||||
if let ModuleDeclaration::ExportNamedDeclaration(decl) =
|
||||
stmt.to_module_declaration_mut()
|
||||
{
|
||||
if let Some(Declaration::TSEnumDeclaration(ts_enum_decl)) = &decl.declaration {
|
||||
self.r#enum.transform_ts_enum(ts_enum_decl, true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(new_stmt) = new_stmt {
|
||||
*stmt = new_stmt;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_if_statement(&mut self, stmt: &mut IfStatement<'a>) {
|
||||
self.annotations.transform_if_statement(stmt);
|
||||
}
|
||||
|
|
@ -163,11 +199,6 @@ impl<'a> TypeScript<'a> {
|
|||
{
|
||||
*decl = self.transform_ts_import_equals(ts_import_equals);
|
||||
}
|
||||
Declaration::TSEnumDeclaration(ts_enum_declaration) => {
|
||||
if let Some(expr) = self.transform_ts_enum(ts_enum_declaration) {
|
||||
*decl = expr;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
use oxc_syntax::{identifier::is_identifier_name, keyword::is_keyword};
|
||||
|
||||
pub fn is_valid_identifier(name: &str, reserved: bool) -> bool {
|
||||
if reserved && (is_keyword(name) || is_reserved_word(name, true)) {
|
||||
return false;
|
||||
}
|
||||
is_identifier_name(name)
|
||||
}
|
||||
|
||||
pub fn is_reserved_word(name: &str, in_module: bool) -> bool {
|
||||
(in_module && name == "await") || name == "enum"
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
Passed: 274/347
|
||||
Passed: 291/364
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-react-jsx-source
|
||||
|
|
@ -23,19 +23,19 @@ Passed: 274/347
|
|||
* opts/optimizeConstEnums/input.ts
|
||||
* opts/rewriteImportExtensions/input.ts
|
||||
|
||||
# babel-plugin-transform-typescript (101/139)
|
||||
# babel-plugin-transform-typescript (118/156)
|
||||
* class/accessor-allowDeclareFields-false/input.ts
|
||||
* class/accessor-allowDeclareFields-true/input.ts
|
||||
* enum/mix-references/input.ts
|
||||
* enum/scoped/input.ts
|
||||
* enum/ts5.0-const-foldable/input.ts
|
||||
* exports/declared-types/input.ts
|
||||
* exports/export-const-enums/input.ts
|
||||
* exports/export-type-star-from/input.ts
|
||||
* imports/enum-id/input.ts
|
||||
* imports/enum-value/input.ts
|
||||
* imports/type-only-export-specifier-2/input.ts
|
||||
* namespace/ambient-module-nested/input.ts
|
||||
* namespace/ambient-module-nested-exported/input.ts
|
||||
* namespace/canonical/input.ts
|
||||
* namespace/clobber-enum/input.ts
|
||||
* namespace/contentious-names/input.ts
|
||||
* namespace/empty-removed/input.ts
|
||||
* namespace/module-nested/input.ts
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
Passed: 1/1
|
||||
Passed: 2/2
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-typescript
|
||||
* babel-plugin-transform-react-jsx
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -120,8 +120,6 @@ pub(crate) const PLUGINS_NOT_SUPPORTED_YET: &[&str] = &[
|
|||
"transform-react-constant-elements",
|
||||
];
|
||||
|
||||
const EXCLUDE_TESTS: &[&str] = &["babel-plugin-transform-typescript/test/fixtures/enum"];
|
||||
|
||||
const CONFORMANCE_SNAPSHOT: &str = "babel.snap.md";
|
||||
const OXC_CONFORMANCE_SNAPSHOT: &str = "oxc.snap.md";
|
||||
const EXEC_SNAPSHOT: &str = "babel_exec.snap.md";
|
||||
|
|
@ -186,9 +184,6 @@ impl TestRunner {
|
|||
return None;
|
||||
}
|
||||
}
|
||||
if EXCLUDE_TESTS.iter().any(|p| path.to_string_lossy().contains(p)) {
|
||||
return None;
|
||||
}
|
||||
TestCaseKind::new(&cwd, path)
|
||||
.filter(|test_case| !test_case.skip_test_case())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
mod ts_fixtures;
|
||||
|
||||
use oxc_transform_conformance::{TestRunner, TestRunnerOptions};
|
||||
use pico_args::Arguments;
|
||||
use ts_fixtures::TypeScriptFixtures;
|
||||
|
||||
fn main() {
|
||||
let mut args = Arguments::from_env();
|
||||
|
|
@ -13,5 +10,4 @@ fn main() {
|
|||
};
|
||||
|
||||
TestRunner::new(options.clone()).run();
|
||||
TypeScriptFixtures::new(options).run();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{Codegen, CodegenOptions};
|
||||
use oxc_diagnostics::{miette::NamedSource, GraphicalReportHandler, GraphicalTheme};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::SourceType;
|
||||
use oxc_tasks_common::{normalize_path, project_root};
|
||||
use oxc_transform_conformance::TestRunnerOptions;
|
||||
use oxc_transformer::{TransformOptions, Transformer};
|
||||
|
||||
fn root() -> PathBuf {
|
||||
project_root().join("tasks/coverage")
|
||||
}
|
||||
|
||||
fn snap_root() -> PathBuf {
|
||||
project_root().join("tasks/transform_conformance")
|
||||
}
|
||||
|
||||
const CASES: &[&str] = &[
|
||||
"typescript/tests/cases/conformance/enums",
|
||||
"babel/packages/babel-plugin-transform-typescript/test/fixtures/enum",
|
||||
];
|
||||
|
||||
const CONFORMANCE_SNAPSHOT: &str = "typescript.snap.md";
|
||||
|
||||
fn filter_ext(p: &Path) -> bool {
|
||||
p.to_string_lossy().ends_with(".ts")
|
||||
}
|
||||
|
||||
pub struct TypeScriptFixtures {
|
||||
options: TestRunnerOptions,
|
||||
}
|
||||
|
||||
impl TypeScriptFixtures {
|
||||
pub fn new(options: TestRunnerOptions) -> Self {
|
||||
Self { options }
|
||||
}
|
||||
|
||||
pub fn run(self) {
|
||||
let mut snapshot = String::new();
|
||||
|
||||
for case in CASES {
|
||||
for path in Self::glob_files(&root().join(case), self.options.filter.as_ref()) {
|
||||
snapshot.push_str("# ");
|
||||
snapshot.push_str(&normalize_path(path.strip_prefix(&root()).unwrap()));
|
||||
snapshot.push('\n');
|
||||
snapshot.push_str("```");
|
||||
|
||||
let (content, lang) = match Self::transform(&path) {
|
||||
Ok(content) => (content, "typescript"),
|
||||
Err(err) => (err, "error"),
|
||||
};
|
||||
snapshot.push_str(lang);
|
||||
snapshot.push('\n');
|
||||
snapshot.push_str(&content);
|
||||
snapshot.push_str("\n```\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if self.options.filter.is_none() {
|
||||
fs::write(snap_root().join(CONFORMANCE_SNAPSHOT), snapshot).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeScriptFixtures {
|
||||
fn transform_options() -> TransformOptions {
|
||||
// TODO: read options from slash directives
|
||||
TransformOptions::default()
|
||||
}
|
||||
|
||||
fn glob_files(root: &Path, filter: Option<&String>) -> Vec<PathBuf> {
|
||||
let mut list: Vec<PathBuf> = WalkDir::new(root)
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.map(walkdir::DirEntry::into_path)
|
||||
.filter(|p| p.is_file())
|
||||
.filter(|p| filter_ext(p.as_path()))
|
||||
.filter(|p| filter.map_or(true, |f| p.to_string_lossy().contains(f)))
|
||||
.collect();
|
||||
|
||||
list.sort_unstable();
|
||||
|
||||
list
|
||||
}
|
||||
|
||||
fn transform(path: &Path) -> Result<String, String> {
|
||||
let allocator = Allocator::default();
|
||||
let source_text = fs::read_to_string(path).unwrap();
|
||||
let source_type = SourceType::from_path(path).unwrap();
|
||||
let parser_ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
|
||||
let semantic_ret = SemanticBuilder::new(&source_text, source_type)
|
||||
.with_trivias(parser_ret.trivias)
|
||||
.with_check_syntax_error(true)
|
||||
.build_module_record(PathBuf::new(), &parser_ret.program)
|
||||
.build(&parser_ret.program);
|
||||
|
||||
let errors = parser_ret.errors.into_iter().chain(semantic_ret.errors).collect::<Vec<_>>();
|
||||
|
||||
if !errors.is_empty() {
|
||||
let handler =
|
||||
GraphicalReportHandler::new().with_theme(GraphicalTheme::unicode_nocolor());
|
||||
let mut output = String::new();
|
||||
for error in errors {
|
||||
let error = error.with_source_code(NamedSource::new(
|
||||
&normalize_path(path.strip_prefix(&root()).unwrap()),
|
||||
source_text.to_string(),
|
||||
));
|
||||
handler.render_report(&mut output, error.as_ref()).unwrap();
|
||||
output.push('\n');
|
||||
}
|
||||
return Err(output);
|
||||
}
|
||||
|
||||
let semantic = semantic_ret.semantic;
|
||||
let transformed_program = allocator.alloc(parser_ret.program);
|
||||
|
||||
let result = Transformer::new(&allocator, path, semantic, Self::transform_options())
|
||||
.build(transformed_program);
|
||||
|
||||
result
|
||||
.map(|()| {
|
||||
Codegen::<false>::new("", &source_text, CodegenOptions::default())
|
||||
.build(transformed_program)
|
||||
.source_text
|
||||
})
|
||||
.map_err(|e| e.iter().map(ToString::to_string).collect())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
enum A {
|
||||
a = Infinity,
|
||||
b,
|
||||
c = Infinity + 1,
|
||||
d = Infinity + "test",
|
||||
e = -(-(-Infinity)),
|
||||
}
|
||||
|
||||
enum B {
|
||||
a = NaN,
|
||||
b,
|
||||
c = NaN + 1,
|
||||
d = "nan" + NaN,
|
||||
e = -NaN,
|
||||
}
|
||||
|
||||
enum C {
|
||||
a = "test" + 1e20,
|
||||
b = 1e30 + "test",
|
||||
c = "test" + 1234567890987 + "test",
|
||||
}
|
||||
|
||||
enum D {
|
||||
a = +"hello",
|
||||
b = -"hello",
|
||||
c = ~"hello",
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
var A = /*#__PURE__*/function (A) {
|
||||
A[A["a"] = Infinity] = "a";
|
||||
A[A["b"] = Infinity] = "b";
|
||||
A[A["c"] = Infinity] = "c";
|
||||
A["d"] = "Infinitytest";
|
||||
A[A["e"] = -Infinity] = "e";
|
||||
return A;
|
||||
}(A || {});
|
||||
var B = /*#__PURE__*/function (B) {
|
||||
B[B["a"] = NaN] = "a";
|
||||
B[B["b"] = NaN] = "b";
|
||||
B[B["c"] = NaN] = "c";
|
||||
B["d"] = "nanNaN";
|
||||
B[B["e"] = NaN] = "e";
|
||||
return B;
|
||||
}(B || {});
|
||||
var C = /*#__PURE__*/function (C) {
|
||||
C["a"] = "test100000000000000000000";
|
||||
C["b"] = "1e+30test";
|
||||
C["c"] = "test1234567890987test";
|
||||
return C;
|
||||
}(C || {});
|
||||
var D = /*#__PURE__*/function (D) {
|
||||
D["a"] = "hello";
|
||||
D[D["b"] = NaN] = "b";
|
||||
D[D["c"] = -1] = "c";
|
||||
return D;
|
||||
}(D || {});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"plugins": [["transform-typescript"]]
|
||||
}
|
||||
Loading…
Reference in a new issue