Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions crates/swc_ecma_transformer/src/bugfix/edge_default_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Bugfix: Edge Default Param
//!
//! Converts destructured parameters with default values to non-shorthand
//! syntax. This fixes the only arguments-related bug in ES Modules-supporting
//! browsers (Edge 16 & 17). Use this plugin instead of
//! @babel/plugin-transform-parameters when targeting ES Modules.
//!
//! ## Example
//!
//! Input:
//! ```js
//! const f = ({ a = 1 }) => a;
//! ```
//!
//! Output:
//! ```js
//! const f = ({ a: a = 1 }) => a;
//! ```

use swc_ecma_ast::*;
use swc_ecma_hooks::VisitMutHook;

use crate::TraverseCtx;

pub fn hook() -> impl VisitMutHook<TraverseCtx> {
EdgeDefaultParamPass { in_arrow: false }
}

struct EdgeDefaultParamPass {
in_arrow: bool,
}

impl VisitMutHook<TraverseCtx> for EdgeDefaultParamPass {
fn enter_arrow_expr(&mut self, _node: &mut ArrowExpr, _ctx: &mut TraverseCtx) {
self.in_arrow = true;
}

fn exit_arrow_expr(&mut self, _node: &mut ArrowExpr, _ctx: &mut TraverseCtx) {
self.in_arrow = false;
}

fn exit_object_pat(&mut self, n: &mut ObjectPat, _ctx: &mut TraverseCtx) {
if !self.in_arrow {
return;
}

for prop in n.props.iter_mut() {
if let ObjectPatProp::Assign(AssignPatProp {
value: Some(value),
key,
span,
..
}) = prop
{
*prop = ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(key.clone().into()),
value: AssignPat {
span: *span,
left: key.clone().into(),
right: value.clone(),
}
.into(),
});
}
}
Comment on lines +26 to +65
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EdgeDefaultParamPass currently uses a single in_arrow boolean that is set for the entire ArrowExpr traversal. This makes the rewrite run for all ObjectPats anywhere inside an arrow (including in the body), even though the bugfix is documented as targeting destructured parameters only. It also becomes incorrect for nested arrows because exiting an inner arrow sets in_arrow = false while still inside the outer arrow.

To match the intended behavior (and the existing implementation in crates/swc_ecma_compat_bugfixes/src/edge_default_param.rs), restrict the rewrite to ArrowExpr.params only (e.g., perform the transformation directly in enter_arrow_expr by walking node.params), or alternatively track nesting with a counter/stack and only enable the rewrite while visiting the parameter list.

Suggested change
EdgeDefaultParamPass { in_arrow: false }
}
struct EdgeDefaultParamPass {
in_arrow: bool,
}
impl VisitMutHook<TraverseCtx> for EdgeDefaultParamPass {
fn enter_arrow_expr(&mut self, _node: &mut ArrowExpr, _ctx: &mut TraverseCtx) {
self.in_arrow = true;
}
fn exit_arrow_expr(&mut self, _node: &mut ArrowExpr, _ctx: &mut TraverseCtx) {
self.in_arrow = false;
}
fn exit_object_pat(&mut self, n: &mut ObjectPat, _ctx: &mut TraverseCtx) {
if !self.in_arrow {
return;
}
for prop in n.props.iter_mut() {
if let ObjectPatProp::Assign(AssignPatProp {
value: Some(value),
key,
span,
..
}) = prop
{
*prop = ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(key.clone().into()),
value: AssignPat {
span: *span,
left: key.clone().into(),
right: value.clone(),
}
.into(),
});
}
}
EdgeDefaultParamPass {}
}
struct EdgeDefaultParamPass {}
impl VisitMutHook<TraverseCtx> for EdgeDefaultParamPass {
fn enter_arrow_expr(&mut self, node: &mut ArrowExpr, _ctx: &mut TraverseCtx) {
fn transform_object_pat(n: &mut ObjectPat) {
for prop in n.props.iter_mut() {
if let ObjectPatProp::Assign(AssignPatProp {
value: Some(value),
key,
span,
..
}) = prop
{
*prop = ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(key.clone().into()),
value: AssignPat {
span: *span,
left: key.clone().into(),
right: value.clone(),
}
.into(),
});
}
}
}
fn transform_pat(pat: &mut Pat) {
match pat {
Pat::Object(obj) => {
transform_object_pat(obj);
}
Pat::Assign(AssignPat { left, .. }) => {
transform_pat(left);
}
Pat::Array(array) => {
for elem in &mut array.elems {
if let Some(inner) = elem {
transform_pat(inner);
}
}
}
_ => {}
}
}
for param in &mut node.params {
transform_pat(param);
}

Copilot uses AI. Check for mistakes.
}
}
34 changes: 24 additions & 10 deletions crates/swc_ecma_transformer/src/bugfix/mod.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
use swc_ecma_hooks::VisitMutHook;

use crate::TraverseCtx;
use crate::{
hook_utils::{HookBuilder, NoopHook},
TraverseCtx,
};

mod edge_default_param;

#[derive(Debug, Default)]
#[non_exhaustive]
pub struct BugfixOptions {}
pub struct BugfixOptions {
/// Enable edge default param bugfix.
///
/// Converts destructured parameters with default values to non-shorthand
/// syntax. This fixes the only arguments-related bug in ES
/// Modules-supporting browsers (Edge 16 & 17).
pub edge_default_param: bool,
}

impl BugfixOptions {
/// Returns true if any transform is enabled.
/// Currently no bugfixes are available.
pub fn is_enabled(&self) -> bool {
false
self.edge_default_param
}
}

pub fn hook(options: BugfixOptions) -> impl VisitMutHook<TraverseCtx> {
BugfixPass { options }
}
let hook = HookBuilder::new(NoopHook);

struct BugfixPass {
options: BugfixOptions,
}
// Edge default param: { a = 1 } -> { a: a = 1 }
let hook = hook.chain(if options.edge_default_param {
Some(self::edge_default_param::hook())
Comment on lines 28 to +33
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says this change “Closes #10470”, but the linked issue is about reducing the number of AST visitors in the ecma minifier, while this PR adds a new bugfix hook in swc_ecma_transformer. If #10470 isn’t actually resolved by this change, please remove the auto-close reference or link the correct issue.

Copilot uses AI. Check for mistakes.
} else {
None
});

impl VisitMutHook<TraverseCtx> for BugfixPass {}
hook.build()
}
Comment on lines 10 to +39
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces new behavior behind BugfixOptions::edge_default_param, but there are no tests exercising the hook-based implementation in swc_ecma_transformer (the existing tests in swc_ecma_compat_bugfixes cover the separate VisitMut pass, not this hook). Consider adding a small set of regression tests (e.g., snapshot tests) that run swc_ecma_transformer::Options with env.bugfix.edge_default_param = true/false and cover at least the default-value case, the no-default case, and a nested pattern case.

Copilot uses AI. Check for mistakes.
3 changes: 3 additions & 0 deletions crates/swc_ecma_transformer/src/hook_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ where
exit_module_export_name,
ModuleExportName
);

// Bugfix hooks: object pattern handling
chained_method!(enter_object_pat, exit_object_pat, ObjectPat);
}

pub(crate) struct NoopHook;
Expand Down
Loading