diff --git a/crates/oxc_compat/src/es_features.rs b/crates/oxc_compat/src/es_features.rs index fa6afd916ab29..9913a41ba812a 100644 --- a/crates/oxc_compat/src/es_features.rs +++ b/crates/oxc_compat/src/es_features.rs @@ -53,6 +53,7 @@ pub enum ESFeature { ES2024UnicodeSetsRegex, ES2025DuplicateNamedCapturingGroupsRegex, ES2025RegexpModifiers, + ES2026ExplicitResourceManagement, } pub fn features() -> &'static FxHashMap { use ESFeature::*; @@ -875,6 +876,16 @@ pub fn features() -> &'static FxHashMap { (Es, Version(2025u16, 0, 0)), ])), ), + ( + ES2026ExplicitResourceManagement, + EngineTargets::new(FxHashMap::from_iter([ + (Chrome, Version(134u16, 0u16, 0u16)), + (Node, Version(24u16, 0u16, 0u16)), + (Firefox, Version(141u16, 0u16, 0u16)), + (Edge, Version(134u16, 0u16, 0u16)), + (Es, Version(2026u16, 0, 0)), + ])), + ), ]) }) } diff --git a/crates/oxc_compat/src/es_target.rs b/crates/oxc_compat/src/es_target.rs index e2ca4d7d68792..4f051b1faa102 100644 --- a/crates/oxc_compat/src/es_target.rs +++ b/crates/oxc_compat/src/es_target.rs @@ -20,6 +20,7 @@ impl ESVersion for ESTarget { Self::ES2023 => Version(2023, 0, 0), Self::ES2024 => Version(2024, 0, 0), Self::ES2025 => Version(2025, 0, 0), + Self::ES2026 => Version(2026, 0, 0), Self::ESNext => Version(9999, 0, 0), } } diff --git a/crates/oxc_syntax/src/es_target.rs b/crates/oxc_syntax/src/es_target.rs index a131ac21fa8e0..de5c15f2c03f0 100644 --- a/crates/oxc_syntax/src/es_target.rs +++ b/crates/oxc_syntax/src/es_target.rs @@ -18,6 +18,7 @@ pub enum ESTarget { ES2023, ES2024, ES2025, + ES2026, #[default] ESNext, } @@ -39,6 +40,7 @@ impl FromStr for ESTarget { "es2023" => Ok(Self::ES2023), "es2024" => Ok(Self::ES2024), "es2025" => Ok(Self::ES2025), + "es2026" => Ok(Self::ES2026), "esnext" => Ok(Self::ESNext), _ => Err(format!("Invalid target \"{s}\".")), } @@ -59,6 +61,7 @@ impl fmt::Display for ESTarget { Self::ES2023 => "es2023", Self::ES2024 => "es2024", Self::ES2025 => "es2025", + Self::ES2026 => "es2026", Self::ESNext => "esnext", }; f.write_str(s) diff --git a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs b/crates/oxc_transformer/src/es2026/explicit_resource_management.rs similarity index 100% rename from crates/oxc_transformer/src/proposals/explicit_resource_management.rs rename to crates/oxc_transformer/src/es2026/explicit_resource_management.rs diff --git a/crates/oxc_transformer/src/es2026/mod.rs b/crates/oxc_transformer/src/es2026/mod.rs new file mode 100644 index 0000000000000..3dfb7e77d9108 --- /dev/null +++ b/crates/oxc_transformer/src/es2026/mod.rs @@ -0,0 +1,70 @@ +use oxc_ast::ast::*; +use oxc_traverse::Traverse; + +use crate::{ + context::{TransformCtx, TraverseCtx}, + state::TransformState, +}; + +mod explicit_resource_management; +mod options; + +use explicit_resource_management::ExplicitResourceManagement; +pub use options::ES2026Options; + +pub struct ES2026<'a, 'ctx> { + explicit_resource_management: Option>, +} + +impl<'a, 'ctx> ES2026<'a, 'ctx> { + pub fn new(options: ES2026Options, ctx: &'ctx TransformCtx<'a>) -> Self { + let explicit_resource_management = if options.explicit_resource_management { + Some(ExplicitResourceManagement::new(ctx)) + } else { + None + }; + Self { explicit_resource_management } + } +} + +impl<'a> Traverse<'a, TransformState<'a>> for ES2026<'a, '_> { + fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(explicit_resource_management) = &mut self.explicit_resource_management { + explicit_resource_management.enter_program(program, ctx); + } + } + + fn enter_for_of_statement( + &mut self, + for_of_stmt: &mut ForOfStatement<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if let Some(explicit_resource_management) = &mut self.explicit_resource_management { + explicit_resource_management.enter_for_of_statement(for_of_stmt, ctx); + } + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(explicit_resource_management) = &mut self.explicit_resource_management { + explicit_resource_management.exit_static_block(block, ctx); + } + } + + fn enter_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(explicit_resource_management) = &mut self.explicit_resource_management { + explicit_resource_management.enter_function_body(body, ctx); + } + } + + fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(explicit_resource_management) = &mut self.explicit_resource_management { + explicit_resource_management.enter_statement(stmt, ctx); + } + } + + fn enter_try_statement(&mut self, node: &mut TryStatement<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(explicit_resource_management) = &mut self.explicit_resource_management { + explicit_resource_management.enter_try_statement(node, ctx); + } + } +} diff --git a/crates/oxc_transformer/src/es2026/options.rs b/crates/oxc_transformer/src/es2026/options.rs new file mode 100644 index 0000000000000..6c9d341c10973 --- /dev/null +++ b/crates/oxc_transformer/src/es2026/options.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Debug, Default, Clone, Copy, Deserialize)] +#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +pub struct ES2026Options { + #[serde(skip)] + pub explicit_resource_management: bool, +} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 998a455e64076..858500f8f95e4 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -31,6 +31,7 @@ mod es2019; mod es2020; mod es2021; mod es2022; +mod es2026; mod jsx; mod proposals; mod regexp; @@ -50,8 +51,8 @@ use es2019::ES2019; use es2020::ES2020; use es2021::ES2021; use es2022::ES2022; +use es2026::ES2026; use jsx::Jsx; -use proposals::ExplicitResourceManagement; use regexp::RegExp; use rustc_hash::FxHashMap; use state::TransformState; @@ -70,6 +71,7 @@ pub use crate::{ es2020::ES2020Options, es2021::ES2021Options, es2022::{ClassPropertiesOptions, ES2022Options}, + es2026::ES2026Options, jsx::{JsxOptions, JsxRuntime, ReactRefreshOptions}, options::{ ESTarget, Engine, EngineTargets, EnvOptions, Module, TransformOptions, @@ -98,6 +100,7 @@ pub struct Transformer<'a> { plugins: PluginsOptions, jsx: JsxOptions, env: EnvOptions, + #[expect(dead_code)] proposals: ProposalOptions, } @@ -140,15 +143,12 @@ impl<'a> Transformer<'a> { common: Common::new(&self.env, &self.ctx), decorator: Decorator::new(self.decorator, &self.ctx), plugins: Plugins::new(self.plugins, &self.ctx), - explicit_resource_management: self - .proposals - .explicit_resource_management - .then(|| ExplicitResourceManagement::new(&self.ctx)), x0_typescript: program .source_type .is_typescript() .then(|| TypeScript::new(&self.typescript, &self.ctx)), x1_jsx: Jsx::new(self.jsx, self.env.es2018.object_rest_spread, ast_builder, &self.ctx), + x2_es2026: ES2026::new(self.env.es2026, &self.ctx), x2_es2022: ES2022::new( self.env.es2022, !self.typescript.allow_declare_fields @@ -178,8 +178,8 @@ struct TransformerImpl<'a, 'ctx> { x0_typescript: Option>, decorator: Decorator<'a, 'ctx>, plugins: Plugins<'a, 'ctx>, - explicit_resource_management: Option>, x1_jsx: Jsx<'a, 'ctx>, + x2_es2026: ES2026<'a, 'ctx>, x2_es2022: ES2022<'a, 'ctx>, x2_es2021: ES2021<'a, 'ctx>, x2_es2020: ES2020<'a, 'ctx>, @@ -200,9 +200,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { } self.plugins.enter_program(program, ctx); self.x1_jsx.enter_program(program, ctx); - if let Some(explicit_resource_management) = self.explicit_resource_management.as_mut() { - explicit_resource_management.enter_program(program, ctx); - } + self.x2_es2026.enter_program(program, ctx); } fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { @@ -316,9 +314,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { self.common.exit_static_block(block, ctx); - if let Some(explicit_resource_management) = self.explicit_resource_management.as_mut() { - explicit_resource_management.exit_static_block(block, ctx); - } + self.x2_es2026.exit_static_block(block, ctx); self.x2_es2022.exit_static_block(block, ctx); } @@ -409,9 +405,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { fn enter_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) { self.common.enter_function_body(body, ctx); - if let Some(explicit_resource_management) = self.explicit_resource_management.as_mut() { - explicit_resource_management.enter_function_body(body, ctx); - } + self.x2_es2026.enter_function_body(body, ctx); } fn exit_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) { @@ -603,9 +597,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { typescript.enter_statement(stmt, ctx); } self.x2_es2018.enter_statement(stmt, ctx); - if let Some(explicit_resource_management) = self.explicit_resource_management.as_mut() { - explicit_resource_management.enter_statement(stmt, ctx); - } + self.x2_es2026.enter_statement(stmt, ctx); } fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) { @@ -646,9 +638,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.enter_for_of_statement(stmt, ctx); } - if let Some(explicit_resource_management) = self.explicit_resource_management.as_mut() { - explicit_resource_management.enter_for_of_statement(stmt, ctx); - } + self.x2_es2026.enter_for_of_statement(stmt, ctx); self.x2_es2018.enter_for_of_statement(stmt, ctx); } @@ -660,9 +650,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { } fn enter_try_statement(&mut self, stmt: &mut TryStatement<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(explicit_resource_management) = self.explicit_resource_management.as_mut() { - explicit_resource_management.enter_try_statement(stmt, ctx); - } + self.x2_es2026.enter_try_statement(stmt, ctx); } fn enter_catch_clause(&mut self, clause: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) { diff --git a/crates/oxc_transformer/src/options/babel/plugins.rs b/crates/oxc_transformer/src/options/babel/plugins.rs index ca8d0ec25e61c..26999b7096b66 100644 --- a/crates/oxc_transformer/src/options/babel/plugins.rs +++ b/crates/oxc_transformer/src/options/babel/plugins.rs @@ -70,10 +70,10 @@ pub struct BabelPlugins { // ES2022 pub class_static_block: bool, pub class_properties: Option, + // ES2026 + pub explicit_resource_management: bool, // Decorator pub legacy_decorator: Option, - // Proposals - pub explicit_resource_management: bool, // Built-in plugins pub styled_components: Option, } diff --git a/crates/oxc_transformer/src/options/env.rs b/crates/oxc_transformer/src/options/env.rs index 9097f4cb2a89d..d2cb7c1e3df6c 100644 --- a/crates/oxc_transformer/src/options/env.rs +++ b/crates/oxc_transformer/src/options/env.rs @@ -9,6 +9,7 @@ use crate::{ es2020::ES2020Options, es2021::ES2021Options, es2022::{ClassPropertiesOptions, ES2022Options}, + es2026::ES2026Options, regexp::RegExpOptions, }; @@ -38,6 +39,8 @@ pub struct EnvOptions { pub es2021: ES2021Options, pub es2022: ES2022Options, + + pub es2026: ES2026Options, } impl EnvOptions { @@ -84,6 +87,7 @@ impl EnvOptions { class_static_block: true, class_properties: Some(ClassPropertiesOptions::default()), }, + es2026: ES2026Options { explicit_resource_management: true }, } } @@ -161,6 +165,9 @@ impl From for EnvOptions { class_static_block: o.has_feature(ES2022ClassStaticBlock), class_properties: o.has_feature(ES2022ClassProperties).then(Default::default), }, + es2026: ES2026Options { + explicit_resource_management: o.has_feature(ES2026ExplicitResourceManagement), + }, } } } diff --git a/crates/oxc_transformer/src/options/mod.rs b/crates/oxc_transformer/src/options/mod.rs index c99a9e4ccf582..3bee209d188c0 100644 --- a/crates/oxc_transformer/src/options/mod.rs +++ b/crates/oxc_transformer/src/options/mod.rs @@ -13,6 +13,7 @@ use crate::{ es2020::ES2020Options, es2021::ES2021Options, es2022::ES2022Options, + es2026::ES2026Options, jsx::JsxOptions, plugins::{PluginsOptions, StyledComponentsOptions}, proposals::ProposalOptions, @@ -277,10 +278,11 @@ impl TryFrom<&BabelOptions> for TransformOptions { es2020, es2021, es2022, + es2026: ES2026Options { + explicit_resource_management: options.plugins.explicit_resource_management, + }, }, - proposals: ProposalOptions { - explicit_resource_management: options.plugins.explicit_resource_management, - }, + proposals: ProposalOptions::default(), helper_loader, plugins, }) diff --git a/crates/oxc_transformer/src/proposals/mod.rs b/crates/oxc_transformer/src/proposals/mod.rs index abb076f874b59..a50257a098c04 100644 --- a/crates/oxc_transformer/src/proposals/mod.rs +++ b/crates/oxc_transformer/src/proposals/mod.rs @@ -1,5 +1,3 @@ -mod explicit_resource_management; mod options; -pub use explicit_resource_management::ExplicitResourceManagement; pub use options::ProposalOptions; diff --git a/crates/oxc_transformer/src/proposals/options.rs b/crates/oxc_transformer/src/proposals/options.rs index 7b929025031f8..de553c80c6dc6 100644 --- a/crates/oxc_transformer/src/proposals/options.rs +++ b/crates/oxc_transformer/src/proposals/options.rs @@ -1,10 +1,5 @@ -#[derive(Debug, Clone, Copy)] +#[expect(clippy::empty_structs_with_brackets)] +#[derive(Debug, Clone, Copy, Default)] pub struct ProposalOptions { - pub explicit_resource_management: bool, -} - -impl Default for ProposalOptions { - fn default() -> Self { - Self { explicit_resource_management: true } - } + // Currently no proposals are configured here. } diff --git a/napi/transform/test/transform.test.ts b/napi/transform/test/transform.test.ts index 00b07aee9a226..20d8c48ee1b76 100644 --- a/napi/transform/test/transform.test.ts +++ b/napi/transform/test/transform.test.ts @@ -113,6 +113,7 @@ describe('target', () => { ['es2019', 'a?.b;\n'], ['es2019', 'a ?? b;\n'], ['es2021', 'class foo {\n\tstatic {}\n}\n'], + ['es2025', 'using handlerSync = openSync();\n'], ]; test.each(data)('transform %s', (target, code) => { diff --git a/tasks/compat_data/data.json b/tasks/compat_data/data.json index d851d23c2f654..586d470aef0fe 100644 --- a/tasks/compat_data/data.json +++ b/tasks/compat_data/data.json @@ -1034,5 +1034,19 @@ "samsung": "27", "electron": "31.0" } + }, + { + "name": "ExplicitResourceManagement", + "babel": "transform-explicit-resource-management", + "features": [ + "Explicit Resource Management" + ], + "es": "ES2026", + "targets": { + "chrome": "134", + "edge": "134", + "firefox": "141", + "node": "24" + } } ] diff --git a/tasks/compat_data/es-features.js b/tasks/compat_data/es-features.js index fd31390efc8ae..dffc86f2340d1 100644 --- a/tasks/compat_data/es-features.js +++ b/tasks/compat_data/es-features.js @@ -325,6 +325,14 @@ const es2025 = [ }, ].map(f('ES2025')); +const es2026 = [ + { + name: 'ExplicitResourceManagement', + babel: 'transform-explicit-resource-management', + features: ['Explicit Resource Management'], + }, +].map(f('ES2026')); + module.exports = [ ...es5, ...es2015, @@ -337,4 +345,5 @@ module.exports = [ ...es2022, ...es2024, ...es2025, + ...es2026, ];