use oxc::allocator::GetAllocator;
use oxc::{
allocator::TakeIn as _,
ast::ast::{
ArrayAssignmentTarget, ArrayExpression, ArrayExpressionElement, AssignmentTarget,
AssignmentTargetMaybeDefault, AssignmentTargetRest, AssignmentTargetWithDefault,
BindingPattern, Elision, Expression, IdentifierReference, ObjectAssignmentTarget,
ObjectExpression, ObjectProperty, ObjectPropertyKind, PropertyKind, SpreadElement,
},
span::SPAN,
};
use crate::AstFactory;
use super::binding_property_ext::BindingPropertyExt as _;
pub trait BindingPatternExt<'ast> {
fn into_assignment_target(self, ast_factory: &AstFactory<'ast>) -> AssignmentTarget<'ast>;
fn into_expression(self, ast_factory: &AstFactory<'ast>) -> Expression<'ast>;
}
impl<'ast> BindingPatternExt<'ast> for BindingPattern<'ast> {
fn into_assignment_target(self, ast_factory: &AstFactory<'ast>) -> AssignmentTarget<'ast> {
match self {
BindingPattern::BindingIdentifier(id) => AssignmentTarget::AssignmentTargetIdentifier(
IdentifierReference::boxed(id.span, id.name, ast_factory),
),
BindingPattern::ObjectPattern(mut obj_pat) => {
let rest = obj_pat.rest.take().map(|rest| {
AssignmentTargetRest::boxed(
rest.span,
rest.unbox().argument.into_assignment_target(ast_factory),
ast_factory,
)
});
let mut properties =
oxc::allocator::Vec::with_capacity_in(obj_pat.properties.len(), ast_factory);
obj_pat.properties.take_in(&ast_factory.allocator()).into_iter().for_each(|binding_prop| {
properties.push(binding_prop.into_assignment_target_property(ast_factory));
});
AssignmentTarget::ObjectAssignmentTarget(ObjectAssignmentTarget::boxed(
SPAN,
properties,
rest,
ast_factory,
))
}
BindingPattern::ArrayPattern(mut arr_pat) => {
let rest = arr_pat.rest.take().map(|rest| {
AssignmentTargetRest::boxed(
rest.span,
rest.unbox().argument.into_assignment_target(ast_factory),
ast_factory,
)
});
let mut elements =
oxc::allocator::Vec::with_capacity_in(arr_pat.elements.len(), ast_factory);
arr_pat.elements.take_in(&ast_factory.allocator()).into_iter().for_each(|binding_pat| {
elements.push(binding_pat.map(|binding_pat| match binding_pat {
BindingPattern::AssignmentPattern(assign_pat) => {
let assign_pat = assign_pat.unbox();
AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(
AssignmentTargetWithDefault::boxed(
assign_pat.span,
assign_pat.left.into_assignment_target(ast_factory),
assign_pat.right,
ast_factory,
),
)
}
_ => {
AssignmentTargetMaybeDefault::from(binding_pat.into_assignment_target(ast_factory))
}
}));
});
AssignmentTarget::ArrayAssignmentTarget(ArrayAssignmentTarget::boxed(
arr_pat.span,
elements,
rest,
ast_factory,
))
}
BindingPattern::AssignmentPattern(_) => {
unreachable!("`BindingPattern::AssignmentPattern` should be pre-handled in above")
}
}
}
fn into_expression(self, ast_factory: &AstFactory<'ast>) -> Expression<'ast> {
match self {
BindingPattern::BindingIdentifier(id) => {
Expression::new_identifier(SPAN, id.name, ast_factory)
}
BindingPattern::ObjectPattern(mut obj_pat) => {
let capacity = obj_pat.properties.len() + usize::from(obj_pat.rest.is_some());
let mut properties = oxc::allocator::Vec::with_capacity_in(capacity, ast_factory);
obj_pat.properties.take_in(&ast_factory.allocator()).into_iter().for_each(|binding_prop| {
properties.push(ObjectPropertyKind::ObjectProperty(ObjectProperty::boxed(
SPAN,
PropertyKind::Init,
binding_prop.key,
binding_prop.value.into_expression(ast_factory),
false,
binding_prop.shorthand,
binding_prop.computed,
ast_factory,
)));
});
if let Some(rest) = obj_pat.rest.take() {
properties.push(ObjectPropertyKind::SpreadProperty(SpreadElement::boxed(
SPAN,
rest.unbox().argument.into_expression(ast_factory),
ast_factory,
)));
}
Expression::ObjectExpression(ObjectExpression::boxed(SPAN, properties, ast_factory))
}
BindingPattern::ArrayPattern(mut arg_pat) => {
let capacity = arg_pat.elements.len() + usize::from(arg_pat.rest.is_some());
let mut elements = oxc::allocator::Vec::with_capacity_in(capacity, ast_factory);
arg_pat.elements.take_in(&ast_factory.allocator()).into_iter().for_each(|binding_pat| {
elements.push(binding_pat.map_or(
ArrayExpressionElement::Elision(Elision::boxed(SPAN, ast_factory)),
|binding_pat| ArrayExpressionElement::from(binding_pat.into_expression(ast_factory)),
));
});
if let Some(rest) = arg_pat.rest.take() {
elements.push(ArrayExpressionElement::SpreadElement(SpreadElement::boxed(
SPAN,
rest.unbox().argument.into_expression(ast_factory),
ast_factory,
)));
}
Expression::ArrayExpression(ArrayExpression::boxed(SPAN, elements, ast_factory))
}
BindingPattern::AssignmentPattern(mut assign_pat) => {
assign_pat.left.take_in(&ast_factory.allocator()).into_expression(ast_factory)
}
}
}
}
#[cfg(test)]
mod tests {
use oxc::{
allocator::{Allocator, CloneIn},
ast::ast::{ArrayExpressionElement, Expression, ObjectPropertyKind, Statement},
parser::Parser,
span::SourceType,
};
use crate::{AstFactory, BindingPatternExt as _};
fn pattern_into_expression<'a>(allocator: &'a Allocator, source: &'a str) -> Expression<'a> {
let program = Parser::new(allocator, source, SourceType::default()).parse().program;
let Some(Statement::VariableDeclaration(decl)) = program.body.first() else {
unreachable!("expected a variable declaration")
};
let pattern = decl.declarations[0].id.clone_in(allocator);
pattern.into_expression(&AstFactory::new(allocator))
}
#[test]
fn object_rest_round_trips_to_spread_property() {
let allocator = Allocator::default();
let Expression::ObjectExpression(obj) =
pattern_into_expression(&allocator, "const { a, ...rest } = x;")
else {
unreachable!("expected an object expression")
};
assert!(matches!(obj.properties.last(), Some(ObjectPropertyKind::SpreadProperty(_))));
}
#[test]
fn array_rest_round_trips_to_spread_element() {
let allocator = Allocator::default();
let Expression::ArrayExpression(arr) =
pattern_into_expression(&allocator, "const [a, ...rest] = x;")
else {
unreachable!("expected an array expression")
};
assert!(matches!(arr.elements.last(), Some(ArrayExpressionElement::SpreadElement(_))));
}
#[test]
fn array_nested_rest_round_trips_to_spread() {
let allocator = Allocator::default();
let Expression::ArrayExpression(arr) =
pattern_into_expression(&allocator, "const [a, ...[b, c]] = x;")
else {
unreachable!("expected an array expression")
};
let Some(ArrayExpressionElement::SpreadElement(spread)) = arr.elements.last() else {
unreachable!("expected a spread element")
};
assert!(matches!(spread.argument, Expression::ArrayExpression(_)));
}
}