Skip to content
Merged
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
51 changes: 29 additions & 22 deletions crates/oxc_transformer/src/es2020/optional_chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,28 +554,7 @@ impl<'a> OptionalChaining<'a, '_> {
// We should generate a temp binding for the expression first to avoid the next step changing the expression.
let temp_binding = self.ctx.var_declarations.create_uid_var_based_on_node(expr, ctx);
if is_call && !self.ctx.assumptions.pure_getters {
if let Some(member) = expr.as_member_expression_mut() {
let object = member.object_mut();
// If the [`MemberExpression::object`] is a global reference, we need to assign it to a temp binding.
// i.e `foo` -> `(_foo = foo)`
if let Expression::Identifier(ident) = object {
let binding =
self.get_existing_binding_for_identifier(ident, ctx).unwrap_or_else(|| {
let binding =
self.ctx.var_declarations.create_uid_var_based_on_node(object, ctx);
// `(_foo = foo)`
*object = Self::create_assignment_expression(
binding.create_write_target(ctx),
ctx.ast.move_expression(object),
ctx,
);
binding.to_maybe_bound_identifier()
});
self.set_binding_context(binding);
} else if matches!(object, Expression::Super(_) | Expression::ThisExpression(_)) {
self.set_this_context();
}
}
self.set_chain_call_context(expr, ctx);
}

// Replace the expression with the temp binding and assign the original expression to the temp binding
Expand Down Expand Up @@ -620,6 +599,7 @@ impl<'a> OptionalChaining<'a, '_> {
if let Some(temp_binding) = self.temp_binding.take() {
self.set_binding_context(temp_binding.to_maybe_bound_identifier());
}
self.set_chain_call_context(expr, ctx);
}

let temp_binding = {
Expand Down Expand Up @@ -651,4 +631,31 @@ impl<'a> OptionalChaining<'a, '_> {
Self::create_logical_expression(left, Self::wrap_void0_check(reference, ctx), ctx)
}
}

fn set_chain_call_context(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(member) = expr.as_member_expression_mut() {
let object = member.object_mut();
// If the [`MemberExpression::object`] is a global reference, we need to assign it to a temp binding.
// i.e `foo` -> `(_foo = foo)`
if matches!(object, Expression::Super(_) | Expression::ThisExpression(_)) {
self.set_this_context();
} else {
let binding = object
.get_identifier_reference()
.and_then(|ident| self.get_existing_binding_for_identifier(ident, ctx))
.unwrap_or_else(|| {
let binding =
self.ctx.var_declarations.create_uid_var_based_on_node(object, ctx);
// `(_foo = foo)`
*object = Self::create_assignment_expression(
binding.create_write_target(ctx),
ctx.ast.move_expression(object),
ctx,
);
binding.to_maybe_bound_identifier()
});
self.set_binding_context(binding);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
function test(foo) {
var _foo$bar, _foo$get, _foo$bar2, _foo$bar3, _foo$bar$baz, _foo$bar$baz2, _foo$bar4, _foo$bar5, _foo$bar6, _foo$bar7, _foo$bar8, _foo$bar8$baz, _foo$bar9, _foo$bar9$baz;
foo == null ? void 0 : foo.bar;
foo == null || (_foo$bar = foo.bar) == null ? void 0 : _foo$bar.baz;
foo == null ? void 0 : foo(foo);
foo == null ? void 0 : foo.bar();
(_foo$get = foo.get(bar)) == null ? void 0 : _foo$get();
(_foo$bar2 = foo.bar()) == null ? void 0 : _foo$bar2();
(_foo$bar3 = foo[bar]()) == null ? void 0 : _foo$bar3();
(_foo$bar$baz = foo.bar().baz) == null ? void 0 : _foo$bar$baz();
(_foo$bar$baz2 = foo[bar]().baz) == null ? void 0 : _foo$bar$baz2();
(_foo$bar4 = foo.bar) == null ? void 0 : _foo$bar4.call(foo, foo.bar, false);
foo == null || (_foo$bar5 = foo.bar) == null ? void 0 : _foo$bar5.call(foo, foo.bar, true);
(_foo$bar6 = foo.bar) == null ? void 0 : _foo$bar6.baz(foo.bar, false);
foo == null || (_foo$bar7 = foo.bar) == null ? void 0 : _foo$bar7.baz(foo.bar, true);
(_foo$bar8 = foo.bar) == null || (_foo$bar8$baz = _foo$bar8.baz) == null ? void 0 : _foo$bar8$baz.call(_foo$bar8, foo.bar, false);
foo == null || (_foo$bar9 = foo.bar) == null || (_foo$bar9$baz = _foo$bar9.baz) == null ? void 0 : _foo$bar9$baz.call(_foo$bar9, foo.bar, true);
var _foo$bar, _foo$get, _foo$bar2, _foo$bar3, _foo$bar$baz, _foo$bar4, _foo$bar$baz2, _foo$bar5, _foo$bar6, _foo$bar7, _foo$bar8, _foo$bar9, _foo$bar10, _foo$bar10$baz, _foo$bar11, _foo$bar11$baz;
foo == null ? void 0 : foo.bar;
foo == null || (_foo$bar = foo.bar) == null ? void 0 : _foo$bar.baz;
foo == null ? void 0 : foo(foo);
foo == null ? void 0 : foo.bar();
(_foo$get = foo.get(bar)) == null ? void 0 : _foo$get();
(_foo$bar2 = foo.bar()) == null ? void 0 : _foo$bar2();
(_foo$bar3 = foo[bar]()) == null ? void 0 : _foo$bar3();
(_foo$bar$baz = (_foo$bar4 = foo.bar()).baz) == null ? void 0 : _foo$bar$baz.call(_foo$bar4);
(_foo$bar$baz2 = (_foo$bar5 = foo[bar]()).baz) == null ? void 0 : _foo$bar$baz2.call(_foo$bar5);
(_foo$bar6 = foo.bar) == null ? void 0 : _foo$bar6.call(foo, foo.bar, false);
foo == null || (_foo$bar7 = foo.bar) == null ? void 0 : _foo$bar7.call(foo, foo.bar, true);
(_foo$bar8 = foo.bar) == null ? void 0 : _foo$bar8.baz(foo.bar, false);
foo == null || (_foo$bar9 = foo.bar) == null ? void 0 : _foo$bar9.baz(foo.bar, true);
(_foo$bar10 = foo.bar) == null || (_foo$bar10$baz = _foo$bar10.baz) == null ? void 0 : _foo$bar10$baz.call(_foo$bar10, foo.bar, false);
foo == null || (_foo$bar11 = foo.bar) == null || (_foo$bar11$baz = _foo$bar11.baz) == null ? void 0 : _foo$bar11$baz.call(_foo$bar11, foo.bar, true);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
function test(foo) {
var _foo$bar, _foo$get, _foo$bar2, _foo$bar3, _foo$bar$baz, _foo$bar$baz2, _foo$bar4, _foo$bar5, _foo$bar6, _foo$bar7, _foo$bar8, _foo$bar8$baz, _foo$bar9, _foo$bar9$baz;
foo === null || foo === void 0 ? void 0 : foo.bar;
foo === null || foo === void 0 || (_foo$bar = foo.bar) === null || _foo$bar === void 0 ? void 0 : _foo$bar.baz;
foo === null || foo === void 0 ? void 0 : foo(foo);
foo === null || foo === void 0 ? void 0 : foo.bar();
(_foo$get = foo.get(bar)) === null || _foo$get === void 0 ? void 0 : _foo$get();
(_foo$bar2 = foo.bar()) === null || _foo$bar2 === void 0 ? void 0 : _foo$bar2();
(_foo$bar3 = foo[bar]()) === null || _foo$bar3 === void 0 ? void 0 : _foo$bar3();
(_foo$bar$baz = foo.bar().baz) === null || _foo$bar$baz === void 0 ? void 0 : _foo$bar$baz();
(_foo$bar$baz2 = foo[bar]().baz) === null || _foo$bar$baz2 === void 0 ? void 0 : _foo$bar$baz2();
(_foo$bar4 = foo.bar) === null || _foo$bar4 === void 0 ? void 0 : _foo$bar4.call(foo, foo.bar, false);
foo === null || foo === void 0 || (_foo$bar5 = foo.bar) === null || _foo$bar5 === void 0 ? void 0 : _foo$bar5.call(foo, foo.bar, true);
(_foo$bar6 = foo.bar) === null || _foo$bar6 === void 0 ? void 0 : _foo$bar6.baz(foo.bar, false);
foo === null || foo === void 0 || (_foo$bar7 = foo.bar) === null || _foo$bar7 === void 0 ? void 0 : _foo$bar7.baz(foo.bar, true);
(_foo$bar8 = foo.bar) === null || _foo$bar8 === void 0 || (_foo$bar8$baz = _foo$bar8.baz) === null || _foo$bar8$baz === void 0 ? void 0 : _foo$bar8$baz.call(_foo$bar8, foo.bar, false);
foo === null || foo === void 0 || (_foo$bar9 = foo.bar) === null || _foo$bar9 === void 0 || (_foo$bar9$baz = _foo$bar9.baz) === null || _foo$bar9$baz === void 0 ? void 0 : _foo$bar9$baz.call(_foo$bar9, foo.bar, true);
var _foo$bar, _foo$get, _foo$bar2, _foo$bar3, _foo$bar$baz, _foo$bar4, _foo$bar$baz2, _foo$bar5, _foo$bar6, _foo$bar7, _foo$bar8, _foo$bar9, _foo$bar10, _foo$bar10$baz, _foo$bar11, _foo$bar11$baz;
foo === null || foo === void 0 ? void 0 : foo.bar;
foo === null || foo === void 0 || (_foo$bar = foo.bar) === null || _foo$bar === void 0 ? void 0 : _foo$bar.baz;
foo === null || foo === void 0 ? void 0 : foo(foo);
foo === null || foo === void 0 ? void 0 : foo.bar();
(_foo$get = foo.get(bar)) === null || _foo$get === void 0 ? void 0 : _foo$get();
(_foo$bar2 = foo.bar()) === null || _foo$bar2 === void 0 ? void 0 : _foo$bar2();
(_foo$bar3 = foo[bar]()) === null || _foo$bar3 === void 0 ? void 0 : _foo$bar3();
(_foo$bar$baz = (_foo$bar4 = foo.bar()).baz) === null || _foo$bar$baz === void 0 ? void 0 : _foo$bar$baz.call(_foo$bar4);
(_foo$bar$baz2 = (_foo$bar5 = foo[bar]()).baz) === null || _foo$bar$baz2 === void 0 ? void 0 : _foo$bar$baz2.call(_foo$bar5);
(_foo$bar6 = foo.bar) === null || _foo$bar6 === void 0 ? void 0 : _foo$bar6.call(foo, foo.bar, false);
foo === null || foo === void 0 || (_foo$bar7 = foo.bar) === null || _foo$bar7 === void 0 ? void 0 : _foo$bar7.call(foo, foo.bar, true);
(_foo$bar8 = foo.bar) === null || _foo$bar8 === void 0 ? void 0 : _foo$bar8.baz(foo.bar, false);
foo === null || foo === void 0 || (_foo$bar9 = foo.bar) === null || _foo$bar9 === void 0 ? void 0 : _foo$bar9.baz(foo.bar, true);
(_foo$bar10 = foo.bar) === null || _foo$bar10 === void 0 || (_foo$bar10$baz = _foo$bar10.baz) === null || _foo$bar10$baz === void 0 ? void 0 : _foo$bar10$baz.call(_foo$bar10, foo.bar, false);
foo === null || foo === void 0 || (_foo$bar11 = foo.bar) === null || _foo$bar11 === void 0 || (_foo$bar11$baz = _foo$bar11.baz) === null || _foo$bar11$baz === void 0 ? void 0 : _foo$bar11$baz.call(_foo$bar11, foo.bar, true);
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
var _bar;
(_bar = foo.bar) === null || _bar === void 0 ? void 0 : _bar(foo.bar, false);
var _bar, _ref;
(_bar = (_ref = foo).bar) === null || _bar === void 0 ? void 0 : _bar.call(_ref, foo.bar, false);
20 changes: 1 addition & 19 deletions tasks/transform_conformance/snapshots/babel_exec.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ commit: 578ac4df

node: v22.14.0

Passed: 318 of 406 (78.33%)
Passed: 320 of 406 (78.82%)

Failures:

Expand All @@ -26,24 +26,6 @@ At file: ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-cla
AssertionError: expected undefined to be 'hello' // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-nested-class-super-property-in-decorator-exec.test.js:22:28

./fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-in-function-param-with-transform-exec.test.js
TypeError: Cannot convert undefined or null to object
at hasOwnProperty (<anonymous>)
at _classPrivateFieldBase (./npm/runtime/src/helpers/classPrivateFieldLooseBase.js:2:26)
at value (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-in-function-param-with-transform-exec.test.js:63:11)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-in-function-param-with-transform-exec.test.js:44:198
at j (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-in-function-param-with-transform-exec.test.js:45:6)
at Function.test (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-in-function-param-with-transform-exec.test.js:52:11)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-in-function-param-with-transform-exec.test.js:71:6

./fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-member-optional-call-with-transform-exec.test.js
TypeError: Cannot convert undefined or null to object
at hasOwnProperty (<anonymous>)
at _classPrivateFieldBase (./npm/runtime/src/helpers/classPrivateFieldLooseBase.js:2:26)
at value (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-member-optional-call-with-transform-exec.test.js:123:11)
at Function.test (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-member-optional-call-with-transform-exec.test.js:24:134)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-optional-chain-member-optional-call-with-transform-exec.test.js:131:6

./fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-parenthesized-optional-member-call-exec.test.js
TypeError: Cannot read properties of undefined (reading 'bind')
at Foo.test (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-private-loose-parenthesized-optional-member-call-exec.test.js:20:59)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
export class Repro {
class Repro {
test() {
this.y?.();
this.f?.();
this.x.f?.();
this.x.y.f?.();
this.x?.f?.();
this.x?.y.f?.();
this.x.y?.f?.();
this.x?.y?.f?.();
this["x"].f?.();
(0, this).f?.();
Comment thread
Dunqing marked this conversation as resolved.
}
}

const repro = {};
repro.f?.();
repro.x.f?.();
repro.x.y.f?.();
repro.x?.f?.();
repro.x?.y.f?.();
repro.x.y?.f?.();
repro.x?.y?.f?.();
repro["x"].f?.();
(0, repro).f?.()

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.