mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(transformer): warn BigInt when targeting < ES2020 (#7184)
closes #5822
This commit is contained in:
parent
a579011e37
commit
22898c855a
13 changed files with 136 additions and 25 deletions
|
|
@ -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<Cow<'static, str>>,
|
||||
pub number: Option<Cow<'static, str>>,
|
||||
|
|
@ -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<Vec<LabeledSpan>>,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,7 @@ use serde::Deserialize;
|
|||
pub struct ES2020Options {
|
||||
#[serde(skip)]
|
||||
pub nullish_coalescing_operator: bool,
|
||||
|
||||
#[serde(skip)]
|
||||
pub big_int: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<ESTarget> 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<BabelEnvOptions> 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),
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub enum ESFeature {
|
|||
ES2019OptionalChaining,
|
||||
ES2020NullishCoalescingOperator,
|
||||
ES2020LogicalAssignmentOperators,
|
||||
ES2020BigInt,
|
||||
ES2021NumericSeparator,
|
||||
ES2022PrivateMethods,
|
||||
ES2022ClassProperties,
|
||||
|
|
@ -670,6 +671,23 @@ pub fn features() -> &'static FxHashMap<ESFeature, EngineTargets> {
|
|||
(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([
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>()
|
||||
.join("\n"),
|
||||
};
|
||||
write!(w, "########## {i} {target}\n{case}\n----------\n{result}\n").unwrap();
|
||||
w
|
||||
});
|
||||
|
||||
#[cfg(not(miri))]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<String, Vec<OxcDiagnostic>> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue