Skip to content
Merged
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
343 changes: 169 additions & 174 deletions crates/oxc_transformer/src/es2021/logical_assignment_operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ impl<'a> Traverse<'a> for LogicalAssignmentOperators<'a> {

// TODO: refactor this block, add tests, cover private identifier
match &mut assignment_expr.left {
// `a &&= c` -> `a && (a = c)`
AssignmentTarget::AssignmentTargetIdentifier(ident) => {
let reference = ctx.symbols_mut().get_reference_mut(ident.reference_id().unwrap());
*reference.flags_mut() = ReferenceFlags::Read;
Expand All @@ -128,191 +129,185 @@ impl<'a> Traverse<'a> for LogicalAssignmentOperators<'a> {
),
);
}
left @ match_member_expression!(AssignmentTarget) => {
let member_expr = left.to_member_expression_mut();
let op = AssignmentOperator::Assign;

// `a.b &&= c` -> `var _a; (_a = a).b && (_a.b = c)`
match member_expr {
MemberExpression::StaticMemberExpression(static_expr) => {
if let Some(ident) = self.maybe_generate_memoised(&static_expr.object, ctx)
{
// (_o = o).a
let right = ctx.ast.move_expression(&mut static_expr.object);
let target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(&ident, ReferenceFlags::Write),
),
);
let object = ctx.ast.expression_assignment(SPAN, op, target, right);
left_expr = Expression::from(ctx.ast.member_expression_static(
SPAN,
object,
static_expr.property.clone_in(ctx.ast.allocator),
false,
));

// (_o.a = 1)
let assign_expr = ctx.ast.member_expression_static(
SPAN,
ctx.ast.expression_from_identifier_reference(ident),
static_expr.property.clone_in(ctx.ast.allocator),
false,
);
assign_target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(assign_expr),
);
} else {
// transform `obj.x ||= 1` to `obj.x || (obj.x = 1)`
let object = ctx.ast.move_expression(&mut static_expr.object);

// TODO: We should use static_expr.clone_in instead of cloning the properties,
// but currently clone_in will get rid of IdentifierReference's reference_id
let static_expr_cloned = ctx.ast.static_member_expression(
static_expr.span,
Self::clone_expression(&object, ctx),
static_expr.property.clone_in(ctx.ast.allocator),
static_expr.optional,
);

left_expr = ctx.ast.expression_member(
ctx.ast.member_expression_from_static(static_expr_cloned),
);

let member_expr_moved = ctx.ast.member_expression_static(
static_expr.span,
object,
static_expr.property.clone_in(ctx.ast.allocator),
static_expr.optional,
);

assign_target = AssignmentTarget::from(
ctx.ast
.simple_assignment_target_member_expression(member_expr_moved),
);
};
// `a.b &&= c` -> `var _a; (_a = a).b && (_a.b = c)`
AssignmentTarget::StaticMemberExpression(static_expr) => {
if let Some(ident) = self.maybe_generate_memoised(&static_expr.object, ctx) {
// (_o = o).a
let right = ctx.ast.move_expression(&mut static_expr.object);
let target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(&ident, ReferenceFlags::Write),
),
);
let object = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
target,
right,
);
left_expr = Expression::from(ctx.ast.member_expression_static(
SPAN,
object,
static_expr.property.clone_in(ctx.ast.allocator),
false,
));

// (_o.a = 1)
let assign_expr = ctx.ast.member_expression_static(
SPAN,
ctx.ast.expression_from_identifier_reference(ident),
static_expr.property.clone_in(ctx.ast.allocator),
false,
);
assign_target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(assign_expr),
);
} else {
// transform `obj.x ||= 1` to `obj.x || (obj.x = 1)`
let object = ctx.ast.move_expression(&mut static_expr.object);

// TODO: We should use static_expr.clone_in instead of cloning the properties,
// but currently clone_in will get rid of IdentifierReference's reference_id
let static_expr_cloned = ctx.ast.static_member_expression(
static_expr.span,
Self::clone_expression(&object, ctx),
static_expr.property.clone_in(ctx.ast.allocator),
static_expr.optional,
);

left_expr = ctx.ast.expression_member(
ctx.ast.member_expression_from_static(static_expr_cloned),
);

let member_expr_moved = ctx.ast.member_expression_static(
static_expr.span,
object,
static_expr.property.clone_in(ctx.ast.allocator),
static_expr.optional,
);

assign_target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(member_expr_moved),
);
};
}
// `a[b.y] &&= c;` ->
// `var _a, _b$y; (_a = a)[_b$y = b.y] && (_a[_b$y] = c);`
AssignmentTarget::ComputedMemberExpression(computed_expr) => {
if let Some(ident) = self.maybe_generate_memoised(&computed_expr.object, ctx) {
// (_o = object)
let right = ctx.ast.move_expression(&mut computed_expr.object);
let target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(&ident, ReferenceFlags::Write),
),
);
let object = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
target,
right,
);

let mut expression = ctx.ast.move_expression(&mut computed_expr.expression);

// _b = expression
let property = self.maybe_generate_memoised(&expression, ctx);

if let Some(ref property) = property {
let left = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(property, ReferenceFlags::Write),
),
);
expression = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
left,
expression,
);
}
// `a[b.y] &&= c;` ->
// `var _a, _b$y; (_a = a)[_b$y = b.y] && (_a[_b$y] = c);`
MemberExpression::ComputedMemberExpression(computed_expr) => {
if let Some(ident) =
self.maybe_generate_memoised(&computed_expr.object, ctx)

// _o[_b]
assign_target = AssignmentTarget::from(ctx.ast.member_expression_computed(
SPAN,
ctx.ast.expression_from_identifier_reference(
ctx.clone_identifier_reference(&ident, ReferenceFlags::Read),
),
property.map_or_else(
|| expression.clone_in(ctx.ast.allocator),
|ident| ctx.ast.expression_from_identifier_reference(ident),
),
false,
));

left_expr = Expression::from(
ctx.ast.member_expression_computed(SPAN, object, expression, false),
);
} else {
// transform `obj[++key] ||= 1` to `obj[_key = ++key] || (obj[_key] = 1)`
let property_ident =
self.maybe_generate_memoised(&computed_expr.expression, ctx);

let object = ctx.ast.move_expression(&mut computed_expr.object);
let mut expression = ctx.ast.move_expression(&mut computed_expr.expression);

// TODO: ideally we should use computed_expr.clone_in instead of cloning the properties,
// but currently clone_in will get rid of IdentifierReference's reference_id
let new_compute_expr = ctx.ast.computed_member_expression(
computed_expr.span,
Self::clone_expression(&object, ctx),
{
// (_o = object)
let right = ctx.ast.move_expression(&mut computed_expr.object);
let target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(&ident, ReferenceFlags::Write),
),
);
let object = ctx.ast.expression_assignment(SPAN, op, target, right);

let mut expression =
ctx.ast.move_expression(&mut computed_expr.expression);

// _b = expression
let property = self.maybe_generate_memoised(&expression, ctx);

if let Some(ref property) = property {
// _key = ++key
if let Some(property_ident) = &property_ident {
let left = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(
property,
property_ident,
ReferenceFlags::Write,
),
),
);
expression =
ctx.ast.expression_assignment(SPAN, op, left, expression);
}

// _o[_b]
assign_target =
AssignmentTarget::from(ctx.ast.member_expression_computed(
ctx.ast.expression_assignment(
SPAN,
ctx.ast.expression_from_identifier_reference(
ctx.clone_identifier_reference(
&ident,
ReferenceFlags::Read,
),
),
property.map_or_else(
|| expression.clone_in(ctx.ast.allocator),
|ident| ctx.ast.expression_from_identifier_reference(ident),
),
false,
));

left_expr = Expression::from(
ctx.ast.member_expression_computed(SPAN, object, expression, false),
);
} else {
// transform `obj[++key] ||= 1` to `obj[_key = ++key] || (obj[_key] = 1)`
let property_ident =
self.maybe_generate_memoised(&computed_expr.expression, ctx);

let object = ctx.ast.move_expression(&mut computed_expr.object);
let mut expression =
ctx.ast.move_expression(&mut computed_expr.expression);

// TODO: ideally we should use computed_expr.clone_in instead of cloning the properties,
// but currently clone_in will get rid of IdentifierReference's reference_id
let new_compute_expr = ctx.ast.computed_member_expression(
computed_expr.span,
Self::clone_expression(&object, ctx),
{
// _key = ++key
if let Some(property_ident) = &property_ident {
let left = AssignmentTarget::from(
ctx.ast
.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(
property_ident,
ReferenceFlags::Write,
),
),
);
ctx.ast.expression_assignment(
SPAN,
op,
left,
ctx.ast.move_expression(&mut expression),
)
} else {
Self::clone_expression(&expression, ctx)
}
},
computed_expr.optional,
);

left_expr = ctx.ast.expression_member(
ctx.ast.member_expression_from_computed(new_compute_expr),
);

// obj[_key] = 1
let new_compute_expr = ctx.ast.computed_member_expression(
computed_expr.span,
object,
{
if let Some(property_ident) = property_ident {
ctx.ast.expression_from_identifier_reference(property_ident)
} else {
expression
}
},
computed_expr.optional,
);

assign_target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(
ctx.ast.member_expression_from_computed(new_compute_expr),
),
);
};
}
MemberExpression::PrivateFieldExpression(_) => return,
}
AssignmentOperator::Assign,
left,
ctx.ast.move_expression(&mut expression),
)
} else {
Self::clone_expression(&expression, ctx)
}
},
computed_expr.optional,
);

left_expr = ctx.ast.expression_member(
ctx.ast.member_expression_from_computed(new_compute_expr),
);

// obj[_key] = 1
let new_compute_expr = ctx.ast.computed_member_expression(
computed_expr.span,
object,
{
if let Some(property_ident) = property_ident {
ctx.ast.expression_from_identifier_reference(property_ident)
} else {
expression
}
},
computed_expr.optional,
);

assign_target =
AssignmentTarget::from(ctx.ast.simple_assignment_target_member_expression(
ctx.ast.member_expression_from_computed(new_compute_expr),
));
};
}
// TODO
#[allow(clippy::match_same_arms)]
AssignmentTarget::PrivateFieldExpression(_) => return,
// All other are TypeScript syntax.

// It is a Syntax Error if AssignmentTargetType of LeftHandSideExpression is not simple.
Expand Down