Skip to content

Commit

Permalink
handle chain expression is a callee of CallExpression
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing authored and Boshen committed Nov 19, 2024
1 parent 5d547f5 commit 19c189f
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 71 deletions.
68 changes: 55 additions & 13 deletions crates/oxc_transformer/src/es2020/optional_chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ impl<'a, 'ctx> OptionalChaining<'a, 'ctx> {
///
/// If we haven't collected the context binding, that means this [`CallExpression`] is called by `super.xxx`,
/// so it will return `this` expression because only class-like can use `super`
fn get_function_context(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
if let Some(binding) = &self.context {
fn get_function_context(&mut self, ctx: &mut TraverseCtx<'a>) -> Argument<'a> {
Argument::from(if let Some(binding) = &self.context {
binding.create_read_expression(ctx)
} else {
ctx.ast.expression_this(SPAN)
}
})
}

/// Given an IdentifierReference which is [`CallExpression::callee`] to compare with collected context,
Expand Down Expand Up @@ -287,9 +287,9 @@ impl<'a, 'ctx> OptionalChaining<'a, 'ctx> {
}
_ => return,
};
let Expression::ChainExpression(chain_expr) = chain_expr else { unreachable!() };

let mut element_expr = match chain_expr.unbox().expression {
let Expression::ChainExpression(chain_expr) = chain_expr else { unreachable!() };
let mut right = match chain_expr.unbox().expression {
ChainElement::CallExpression(call) => Expression::CallExpression(call),
element @ match_member_expression!(ChainElement) => {
Expression::from(element.into_member_expression())
Expand All @@ -298,12 +298,21 @@ impl<'a, 'ctx> OptionalChaining<'a, 'ctx> {

self.configure_check_mode(is_delete, ctx);

let left = self.transform_chain_expression_recursion(&mut element_expr, ctx).unwrap();
let right = if is_delete {
// `left && delete element_expr`
ctx.ast.expression_unary(SPAN, UnaryOperator::Delete, element_expr)
} else {
element_expr
let left = self.transform_chain_expression_recursion(&mut right, ctx).unwrap();

// If the chain expression is a argument of a UnaryExpression and its operator is `delete`,
// we need to wrap the last part with a `delete` unary expression
// `delete foo?.bar` -> `... || delete _Foo.bar;`
// ^^^^^^ ^^^^^^^^ Here we will wrap the right part with a `delete` unary expression
if is_delete {
right = ctx.ast.expression_unary(SPAN, UnaryOperator::Delete, right);
};

// If this chain expression is a callee of a CallExpression, we need to transform it to accept a proper context
// `(Foo?.["m"])();` -> `(... _Foo["m"].bind(_Foo))();`
// ^^^^^^^^^^^ Here we will handle `right` part to bind a proper context
if matches!(ctx.ancestor(1), Ancestor::CallExpressionCallee(_)) {
right = self.transform_expression_to_bind_context(right, ctx);
};

let new_expr = if self.is_boolean_mode() || {
Expand Down Expand Up @@ -331,6 +340,40 @@ impl<'a, 'ctx> OptionalChaining<'a, 'ctx> {
*expr = new_expr;
}

/// Transform an expression to bind a proper context
/// `Foo.bar` -> `Foo.bar.bind(context)`
fn transform_expression_to_bind_context(
&mut self,
mut expr: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
// Find proper context
if let Some(member) = expr.as_member_expression_mut() {
let object = member.object_mut().get_inner_expression_mut();
if let Expression::Identifier(ident) = object {
self.set_function_context(MaybeBoundIdentifier::from_identifier_reference(
ident, ctx,
));
} else {
// `foo.bar` -> `_foo&bar = foo.bar`
let binding = self.generate_binding(object, ctx);
*object = Self::create_assignment_expression(
binding.create_read_write_target(ctx),
ctx.ast.move_expression(object),
ctx,
);
self.set_function_context(binding.to_maybe_bound_identifier());
}
}

// `expr.bind(context)`
let arguments = ctx.ast.vec1(self.get_function_context(ctx));
let property = ctx.ast.identifier_name(SPAN, "bind");
let callee = ctx.ast.member_expression_static(SPAN, expr, property, false);
let callee = Expression::from(callee);
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
}

/// Recursive transform chain expression
///
/// TODO: add more details here
Expand Down Expand Up @@ -396,8 +439,7 @@ impl<'a, 'ctx> OptionalChaining<'a, 'ctx> {
ctx.ast.identifier_name(SPAN, "call"),
false,
));
c.arguments
.insert(0, Argument::from(self.get_function_context(ctx)));
c.arguments.insert(0, self.get_function_context(ctx));
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: d20b314c

Passed: 329/679
Passed: 330/679

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down Expand Up @@ -296,7 +296,7 @@ x Output mismatch
x Output mismatch


# babel-plugin-transform-optional-chaining (9/45)
# babel-plugin-transform-optional-chaining (10/45)
* assumption-noDocumentAll/assignment/input.js
x Output mismatch

Expand Down Expand Up @@ -360,9 +360,6 @@ x Output mismatch
* general/optional-eval-call-loose/input.js
x Output mismatch

* general/parenthesized-member-call/input.js
x Output mismatch

* general/parenthesized-member-call-loose/input.js
x Output mismatch

Expand Down
78 changes: 25 additions & 53 deletions tasks/transform_conformance/snapshots/babel_exec.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ Error: 'eval' and 'arguments' cannot be used as a binding identifier in strict m
❯ ssrTransformScript ../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/vite/dist/node/chunks/dep-CDnG8rE7.js:52319:11
❯ loadAndTransform ../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/vite/dist/node/chunks/dep-CDnG8rE7.js:51917:72

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/18]

⎯⎯⎯⎯⎯⎯ Failed Tests 19 ⎯⎯⎯⎯⎯⎯⎯
⎯⎯⎯⎯⎯⎯ Failed Tests 17 ⎯⎯⎯⎯⎯⎯⎯

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-assumption-objectRestNoSymbols-rest-ignore-symbols-exec.test.js > exec
AssertionError: expected true to be false // Object.is equality
Expand All @@ -31,7 +31,7 @@ AssertionError: expected true to be false // Object.is equality
| ^
13| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-assumption-pureGetters-rest-remove-unused-excluded-keys-exec.test.js > exec
AssertionError: expected true to be false // Object.is equality
Expand All @@ -49,7 +49,7 @@ AssertionError: expected true to be false // Object.is equality
| ^
11| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-assumption-pureGetters-spread-single-call-exec.test.js > exec
AssertionError: expected { foo: +0, middle: 1, bar: 1 } to deeply equal { foo: +0, middle: +0, bar: 1 }
Expand All @@ -72,7 +72,7 @@ AssertionError: expected { foo: +0, middle: 1, bar: 1 } to deeply equal { foo: +
13| foo: 0,
14| middle: 0,

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-assumption-setSpreadProperties-no-object-assign-exec-exec.test.js > exec
AssertionError: expected [Function] to throw an error
Expand All @@ -84,7 +84,7 @@ AssertionError: expected [Function] to throw an error
14| const obj2 = { "NOWRITE": 456 };
15| expect(() => {

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-assumption-setSpreadProperties-with-useBuiltIns-no-object-assign-exec-exec.test.js > exec
AssertionError: expected [Function] to throw an error
Expand All @@ -96,7 +96,7 @@ AssertionError: expected [Function] to throw an error
14| const obj2 = { "NOWRITE": 456 };
15| expect(() => {

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-object-spread-expression-exec.test.js > exec
AssertionError: expected [ 1, 2 ] to deeply equal [ 1 ]
Expand All @@ -116,7 +116,7 @@ AssertionError: expected [ 1, 2 ] to deeply equal [ 1 ]
| ^
11| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-object-spread-loose-builtins-side-effect-exec.test.js > exec
AssertionError: expected { a: 1, b: 1 } to deeply equal { a: 2, b: 1 }
Expand All @@ -138,7 +138,7 @@ AssertionError: expected { a: 1, b: 1 } to deeply equal { a: 2, b: 1 }
10| a: 2,
11| b: 1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/18]

FAIL fixtures/babel-plugin-transform-object-rest-spread-test-fixtures-object-spread-loose-side-effect-exec.test.js > exec
AssertionError: expected { a: 1, b: 1 } to deeply equal { a: 2, b: 1 }
Expand All @@ -160,7 +160,7 @@ AssertionError: expected { a: 1, b: 1 } to deeply equal { a: 2, b: 1 }
10| a: 2,
11| b: 1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/18]

FAIL fixtures/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js > exec
TypeError: Cannot read properties of undefined (reading 'x')
Expand All @@ -171,10 +171,10 @@ TypeError: Cannot read properties of undefined (reading 'x')
| ^
11| }
12| getSelf() {
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js:22:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js:65:12
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js:25:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js:68:12

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/18]

FAIL fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-exec.test.js > exec
TypeError: Cannot read properties of undefined (reading 'x')
Expand All @@ -185,10 +185,10 @@ TypeError: Cannot read properties of undefined (reading 'x')
| ^
11| }
12| getSelf() {
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-exec.test.js:22:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-exec.test.js:65:12
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-exec.test.js:25:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-exec.test.js:68:12

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/18]

FAIL fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-loose-exec.test.js > exec
TypeError: Cannot read properties of undefined (reading 'x')
Expand All @@ -199,38 +199,10 @@ TypeError: Cannot read properties of undefined (reading 'x')
| ^
11| }
12| getSelf() {
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-loose-exec.test.js:22:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-loose-exec.test.js:65:12
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-loose-exec.test.js:25:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-expression-member-call-loose-exec.test.js:68:12

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/20]

FAIL fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-exec.test.js > exec
TypeError: Cannot read properties of undefined (reading 'x')
❯ m fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-exec.test.js:10:16
8| }
9| m() {
10| return this.x;
| ^
11| }
12| getSelf() {
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-exec.test.js:22:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-exec.test.js:62:12

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[13/20]

FAIL fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-loose-exec.test.js > exec
TypeError: Cannot read properties of undefined (reading 'x')
❯ m fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-loose-exec.test.js:10:16
8| }
9| m() {
10| return this.x;
| ^
11| }
12| getSelf() {
❯ Foo.test fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-loose-exec.test.js:22:63
❯ fixtures/babel-plugin-transform-optional-chaining-test-fixtures-general-parenthesized-member-call-loose-exec.test.js:62:12

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/18]

FAIL fixtures/babel-plugin-transform-optional-chaining-test-fixtures-regression-15887-exec.test.js > exec
AssertionError: expected false to be undefined // Object.is equality
Expand All @@ -248,7 +220,7 @@ false
| ^
7| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[15/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[13/18]

FAIL fixtures/babel-plugin-transform-react-jsx-source-test-fixtures-react-source-basic-sample-exec.test.js > exec
ReferenceError: transformAsync is not defined
Expand All @@ -260,7 +232,7 @@ ReferenceError: transformAsync is not defined
5| var expected = `
6| var _jsxFileName = "/fake/path/mock.js";

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[16/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/18]

FAIL fixtures/babel-plugin-transform-react-jsx-source-test-fixtures-react-source-with-source-exec.test.js > exec
ReferenceError: transformAsync is not defined
Expand All @@ -272,7 +244,7 @@ ReferenceError: transformAsync is not defined
5| var expected = "var x = <sometag __source=\"custom\" />;";
6| return actualP.then((actual) => {

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[17/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[15/18]

FAIL fixtures/babel-preset-env-test-fixtures-plugins-integration-issue-15170-exec.test.js > exec
AssertionError: expected [Function] to not throw an error but 'ReferenceError: x is not defined' was thrown
Expand All @@ -290,7 +262,7 @@ undefined
| ^
7| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[18/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[16/18]

FAIL fixtures/babel-preset-env-test-fixtures-sanity-check-es2015-constants-exec.test.js > exec
TypeError: Assignment to constant variable.
Expand All @@ -301,7 +273,7 @@ TypeError: Assignment to constant variable.
| ^
6| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[19/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[17/18]

FAIL fixtures/babel-preset-env-test-fixtures-sanity-regex-dot-all-exec.test.js > exec
AssertionError: expected false to be true // Object.is equality
Expand All @@ -320,5 +292,5 @@ AssertionError: expected false to be true // Object.is equality
11| expect(/hello.world/su.test(input)).toBe(true);
12| })

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[20/20]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[18/18]

0 comments on commit 19c189f

Please sign in to comment.