diff --git a/crates/oxc_diagnostics/src/lib.rs b/crates/oxc_diagnostics/src/lib.rs index 8925274c3fe59..25433995f4fc8 100644 --- a/crates/oxc_diagnostics/src/lib.rs +++ b/crates/oxc_diagnostics/src/lib.rs @@ -70,7 +70,7 @@ pub use miette::{GraphicalReportHandler, GraphicalTheme, LabeledSpan, NamedSourc /// Describes an error or warning that occurred. /// /// Used by all oxc tools. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] #[must_use] pub struct OxcDiagnostic { // `Box` the data to make `OxcDiagnostic` 8 bytes so that `Result` is small. @@ -92,7 +92,7 @@ impl DerefMut for OxcDiagnostic { } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct OxcCode { pub scope: Option>, pub number: Option>, @@ -114,7 +114,7 @@ impl fmt::Display for OxcCode { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct OxcDiagnosticInner { pub message: Cow<'static, str>, pub labels: Option>, diff --git a/crates/oxc_transformer/src/es2020/mod.rs b/crates/oxc_transformer/src/es2020/mod.rs index 18cd13af6a6ae..e00599093ee0b 100644 --- a/crates/oxc_transformer/src/es2020/mod.rs +++ b/crates/oxc_transformer/src/es2020/mod.rs @@ -1,4 +1,5 @@ use oxc_ast::ast::*; +use oxc_diagnostics::OxcDiagnostic; use oxc_traverse::{Traverse, TraverseCtx}; use crate::TransformCtx; @@ -10,6 +11,8 @@ pub use nullish_coalescing_operator::NullishCoalescingOperator; pub use options::ES2020Options; pub struct ES2020<'a, 'ctx> { + ctx: &'ctx TransformCtx<'a>, + options: ES2020Options, // Plugins @@ -18,7 +21,7 @@ pub struct ES2020<'a, 'ctx> { impl<'a, 'ctx> ES2020<'a, 'ctx> { pub fn new(options: ES2020Options, ctx: &'ctx TransformCtx<'a>) -> Self { - Self { nullish_coalescing_operator: NullishCoalescingOperator::new(ctx), options } + Self { ctx, nullish_coalescing_operator: NullishCoalescingOperator::new(ctx), options } } } @@ -28,4 +31,14 @@ impl<'a, 'ctx> Traverse<'a> for ES2020<'a, 'ctx> { self.nullish_coalescing_operator.enter_expression(expr, ctx); } } + + fn enter_big_int_literal(&mut self, node: &mut BigIntLiteral<'a>, _ctx: &mut TraverseCtx<'a>) { + if self.options.big_int { + let warning = OxcDiagnostic::warn( + "Big integer literals are not available in the configured target environment.", + ) + .with_label(node.span); + self.ctx.error(warning); + } + } } diff --git a/crates/oxc_transformer/src/es2020/options.rs b/crates/oxc_transformer/src/es2020/options.rs index 494a4d802fe67..78b94930ee25b 100644 --- a/crates/oxc_transformer/src/es2020/options.rs +++ b/crates/oxc_transformer/src/es2020/options.rs @@ -5,4 +5,7 @@ use serde::Deserialize; pub struct ES2020Options { #[serde(skip)] pub nullish_coalescing_operator: bool, + + #[serde(skip)] + pub big_int: bool, } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index bedcf7d7a7c16..9f2f7b1ba2d93 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -171,6 +171,10 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { } } + fn enter_big_int_literal(&mut self, node: &mut BigIntLiteral<'a>, ctx: &mut TraverseCtx<'a>) { + self.x2_es2020.enter_big_int_literal(node, ctx); + } + fn enter_binding_pattern(&mut self, pat: &mut BindingPattern<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.enter_binding_pattern(pat, ctx); diff --git a/crates/oxc_transformer/src/options/env.rs b/crates/oxc_transformer/src/options/env.rs index 4dda3f269aa16..ffec2ce7a1620 100644 --- a/crates/oxc_transformer/src/options/env.rs +++ b/crates/oxc_transformer/src/options/env.rs @@ -117,7 +117,11 @@ impl EnvOptions { async_generator_functions: true, }, es2019: ES2019Options { optional_catch_binding: true }, - es2020: ES2020Options { nullish_coalescing_operator: true }, + es2020: ES2020Options { + nullish_coalescing_operator: true, + // Turn this on would throw error for all bigints. + big_int: false, + }, es2021: ES2021Options { logical_assignment_operators: true }, es2022: ES2022Options { class_static_block: true, @@ -166,7 +170,10 @@ impl From for EnvOptions { async_generator_functions: target < ESTarget::ES2018, }, es2019: ES2019Options { optional_catch_binding: target < ESTarget::ES2019 }, - es2020: ES2020Options { nullish_coalescing_operator: target < ESTarget::ES2020 }, + es2020: ES2020Options { + nullish_coalescing_operator: target < ESTarget::ES2020, + big_int: target < ESTarget::ES2020, + }, es2021: ES2021Options { logical_assignment_operators: target < ESTarget::ES2021 }, es2022: ES2022Options { class_static_block: target < ESTarget::ES2022, @@ -210,6 +217,7 @@ impl TryFrom for EnvOptions { }, es2020: ES2020Options { nullish_coalescing_operator: o.can_enable(ES2020NullishCoalescingOperator), + big_int: o.can_enable(ES2020BigInt), }, es2021: ES2021Options { logical_assignment_operators: o.can_enable(ES2020LogicalAssignmentOperators), diff --git a/crates/oxc_transformer/src/options/es_features.rs b/crates/oxc_transformer/src/options/es_features.rs index c3ace168d34e9..c662950bbc025 100644 --- a/crates/oxc_transformer/src/options/es_features.rs +++ b/crates/oxc_transformer/src/options/es_features.rs @@ -43,6 +43,7 @@ pub enum ESFeature { ES2019OptionalChaining, ES2020NullishCoalescingOperator, ES2020LogicalAssignmentOperators, + ES2020BigInt, ES2021NumericSeparator, ES2022PrivateMethods, ES2022ClassProperties, @@ -670,6 +671,23 @@ pub fn features() -> &'static FxHashMap { (Edge, Version(85u32, 0u32, 0u32)), ])), ), + ( + ES2020BigInt, + EngineTargets::new(FxHashMap::from_iter([ + (Chrome, Version(67u32, 0u32, 0u32)), + (Safari, Version(14u32, 0u32, 0u32)), + (OperaMobile, Version(48u32, 0u32, 0u32)), + (Samsung, Version(9u32, 0u32, 0u32)), + (Node, Version(10u32, 4u32, 0u32)), + (Rhino, Version(1u32, 7u32, 14u32)), + (Firefox, Version(68u32, 0u32, 0u32)), + (Deno, Version(1u32, 0u32, 0u32)), + (Electron, Version(4u32, 0u32, 0u32)), + (Opera, Version(54u32, 0u32, 0u32)), + (Ios, Version(14u32, 0u32, 0u32)), + (Edge, Version(79u32, 0u32, 0u32)), + ])), + ), ( ES2021NumericSeparator, EngineTargets::new(FxHashMap::from_iter([ diff --git a/crates/oxc_transformer/src/options/mod.rs b/crates/oxc_transformer/src/options/mod.rs index 32a9132d41501..2574df6ae9fa2 100644 --- a/crates/oxc_transformer/src/options/mod.rs +++ b/crates/oxc_transformer/src/options/mod.rs @@ -174,6 +174,7 @@ impl TryFrom<&BabelOptions> for TransformOptions { let es2020 = ES2020Options { nullish_coalescing_operator: options.plugins.nullish_coalescing_operator || env.es2020.nullish_coalescing_operator, + big_int: env.es2020.big_int, }; let es2021 = ES2021Options { diff --git a/crates/oxc_transformer/tests/integrations/es_target.rs b/crates/oxc_transformer/tests/integrations/es_target.rs index cffb173ca1653..2c014a6b18688 100644 --- a/crates/oxc_transformer/tests/integrations/es_target.rs +++ b/crates/oxc_transformer/tests/integrations/es_target.rs @@ -16,21 +16,30 @@ fn es_target() { ("es2018", "try {} catch {}"), ("es2019", "a ?? b"), ("es2020", "a ||= b"), + ("es2019", "1n ** 2n"), // test target error ("es2021", "class foo { static {} }"), ]; // Test no transformation for esnext. for (_, case) in cases { let options = TransformOptions::from(ESTarget::from_str("esnext").unwrap()); - assert_eq!(codegen(case, SourceType::mjs()), test(case, options)); + assert_eq!(Ok(codegen(case, SourceType::mjs())), test(case, options)); } - let snapshot = cases.iter().enumerate().fold(String::new(), |mut w, (i, (target, case))| { - let options = TransformOptions::from(ESTarget::from_str(target).unwrap()); - let result = test(case, options); - write!(w, "########## {i} {target}\n{case}\n----------\n{result}\n").unwrap(); - w - }); + let snapshot = + cases.into_iter().enumerate().fold(String::new(), |mut w, (i, (target, case))| { + let options = TransformOptions::from(ESTarget::from_str(target).unwrap()); + let result = match test(case, options) { + Ok(code) => code, + Err(errors) => errors + .into_iter() + .map(|err| format!("{:?}", err.with_source_code(case.to_string()))) + .collect::>() + .join("\n"), + }; + write!(w, "########## {i} {target}\n{case}\n----------\n{result}\n").unwrap(); + w + }); #[cfg(not(miri))] { diff --git a/crates/oxc_transformer/tests/integrations/main.rs b/crates/oxc_transformer/tests/integrations/main.rs index da294218e7526..bd71302e8a7dc 100644 --- a/crates/oxc_transformer/tests/integrations/main.rs +++ b/crates/oxc_transformer/tests/integrations/main.rs @@ -6,6 +6,7 @@ use std::path::Path; use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; +use oxc_diagnostics::OxcDiagnostic; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; @@ -20,20 +21,27 @@ pub fn codegen(source_text: &str, source_type: SourceType) -> String { .code } -pub(crate) fn test(source_text: &str, options: TransformOptions) -> String { +pub(crate) fn test( + source_text: &str, + options: TransformOptions, +) -> Result> { let source_type = SourceType::default(); let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let mut program = ret.program; let (symbols, scopes) = SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); - Transformer::new(&allocator, Path::new(""), options).build_with_symbols_and_scopes( + let ret = Transformer::new(&allocator, Path::new(""), options).build_with_symbols_and_scopes( symbols, scopes, &mut program, ); - CodeGenerator::new() + if !ret.errors.is_empty() { + return Err(ret.errors); + } + let code = CodeGenerator::new() .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) .build(&program) - .code + .code; + Ok(code) } diff --git a/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap b/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap index 6993434205ff4..5d8328015dd29 100644 --- a/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap +++ b/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap @@ -46,7 +46,26 @@ a ||= b ---------- a || (a = b); -########## 7 es2021 +########## 7 es2019 +1n ** 2n +---------- + + ! Big integer literals are not available in the configured target + | environment. + ,---- + 1 | 1n ** 2n + : ^^ + `---- + + + ! Big integer literals are not available in the configured target + | environment. + ,---- + 1 | 1n ** 2n + : ^^ + `---- + +########## 8 es2021 class foo { static {} } ---------- class foo { diff --git a/crates/oxc_transformer/tests/integrations/targets.rs b/crates/oxc_transformer/tests/integrations/targets.rs index eb5edca124f85..e3c107f5cbbd8 100644 --- a/crates/oxc_transformer/tests/integrations/targets.rs +++ b/crates/oxc_transformer/tests/integrations/targets.rs @@ -5,14 +5,15 @@ use oxc_transformer::{ESTarget, EnvOptions, TransformOptions}; #[test] fn targets() { let cases = [ - ("() => {}"), - ("a ** b"), + // ("() => {}"), + // ("a ** b"), // ("async function foo() {}"), - ("({ ...x })"), - ("try {} catch {}"), - ("a ?? b"), - ("a ||= b"), + // ("({ ...x })"), + // ("try {} catch {}"), + // ("a ?? b"), + // ("a ||= b"), // ("class foo { static {} }"), + "1n ** 2n", ]; // Test no transformation for default targets. @@ -21,7 +22,7 @@ fn targets() { env: EnvOptions::from_browserslist_query("defaults").unwrap(), ..TransformOptions::default() }; - assert_eq!(codegen(case, SourceType::mjs()), test(case, options)); + assert_eq!(Ok(codegen(case, SourceType::mjs())), test(case, options)); } // Test transformation for very low targets. diff --git a/tasks/compat_data/data.json b/tasks/compat_data/data.json index c481a1f585c7e..c7b0541973274 100644 --- a/tasks/compat_data/data.json +++ b/tasks/compat_data/data.json @@ -821,6 +821,28 @@ "electron": "10.0" } }, + { + "name": "BigInt", + "babel": null, + "features": [ + "BigInt / basic functionality" + ], + "es": "ES2020", + "targets": { + "chrome": "67", + "opera": "54", + "edge": "79", + "firefox": "68", + "safari": "14", + "node": "10.4", + "deno": "1", + "ios": "14", + "samsung": "9", + "rhino": "1.7.14", + "opera_mobile": "48", + "electron": "4.0" + } + }, { "name": "NumericSeparator", "babel": "transform-numeric-separator", diff --git a/tasks/compat_data/es-features.js b/tasks/compat_data/es-features.js index 09e6b537d2885..856ba4033e6d7 100644 --- a/tasks/compat_data/es-features.js +++ b/tasks/compat_data/es-features.js @@ -247,6 +247,11 @@ const es2020 = [ babel: 'transform-logical-assignment-operators', features: ['Logical Assignment'], }, + { + name: 'BigInt', + babel: null, + features: ['BigInt / basic functionality'], + }, ].map(f('ES2020')); const es2021 = [