diff --git a/crates/oxc_transformer/src/common/arrow_function_converter.rs b/crates/oxc_transformer/src/common/arrow_function_converter.rs index 65ba604c15307..97993ff84f0e5 100644 --- a/crates/oxc_transformer/src/common/arrow_function_converter.rs +++ b/crates/oxc_transformer/src/common/arrow_function_converter.rs @@ -89,9 +89,9 @@ use compact_str::CompactString; use indexmap::IndexMap; -use rustc_hash::{FxBuildHasher, FxHashSet}; +use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; -use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec}; +use oxc_allocator::{Address, Box as ArenaBox, GetAddress, Vec as ArenaVec}; use oxc_ast::{ast::*, NONE}; use oxc_data_structures::stack::{NonEmptyStack, SparseStack}; use oxc_semantic::{ReferenceFlags, SymbolId}; @@ -102,7 +102,7 @@ use oxc_syntax::{ }; use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; -use crate::EnvOptions; +use crate::{context::TransformCtx, EnvOptions}; type FxIndexMap = IndexMap; @@ -135,9 +135,12 @@ struct SuperMethodInfo<'a> { is_computed: bool, } -pub struct ArrowFunctionConverter<'a> { +pub struct ArrowFunctionConverter<'a, 'ctx> { + ctx: &'ctx TransformCtx<'a>, mode: ArrowFunctionConverterMode, this_var_stack: SparseStack>, + /// Stores the address of statement of containing `super()` expression + super_call_addresses: FxHashMap, arguments_var_stack: SparseStack>, arguments_needs_transform_stack: NonEmptyStack, renamed_arguments_symbol_ids: FxHashSet, @@ -146,8 +149,8 @@ pub struct ArrowFunctionConverter<'a> { super_methods: Option, SuperMethodInfo<'a>>>, } -impl<'a> ArrowFunctionConverter<'a> { - pub fn new(env: &EnvOptions) -> Self { +impl<'a, 'ctx> ArrowFunctionConverter<'a, 'ctx> { + pub fn new(env: &EnvOptions, ctx: &'ctx TransformCtx<'a>) -> Self { let mode = if env.es2015.arrow_function.is_some() { ArrowFunctionConverterMode::Enabled } else if env.es2017.async_to_generator || env.es2018.async_generator_functions { @@ -157,8 +160,10 @@ impl<'a> ArrowFunctionConverter<'a> { }; // `SparseStack`s are created with 1 empty entry, for `Program` Self { + ctx, mode, this_var_stack: SparseStack::new(), + super_call_addresses: FxHashMap::default(), arguments_var_stack: SparseStack::new(), arguments_needs_transform_stack: NonEmptyStack::new(false), renamed_arguments_symbol_ids: FxHashSet::default(), @@ -167,7 +172,7 @@ impl<'a> ArrowFunctionConverter<'a> { } } -impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> { +impl<'a, 'ctx> Traverse<'a> for ArrowFunctionConverter<'a, 'ctx> { // Note: No visitors for `TSModuleBlock` because `this` is not legal in TS module blocks. // @@ -397,7 +402,7 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> { } } -impl<'a> ArrowFunctionConverter<'a> { +impl<'a, 'ctx> ArrowFunctionConverter<'a, 'ctx> { /// Check if arrow function conversion is disabled fn is_disabled(&self) -> bool { self.mode == ArrowFunctionConverterMode::Disabled @@ -436,6 +441,26 @@ impl<'a> ArrowFunctionConverter<'a> { .unwrap(); ctx.generate_uid("this", target_scope_id, SymbolFlags::FunctionScopedVariable) }); + + if !self.super_call_addresses.is_empty() { + let address = ctx + .scopes() + .get_parent_id(arrow_scope_id) + .and_then(|scope_id| self.super_call_addresses.remove(&scope_id)); + if let Some(address) = address { + // Insert a dummy address to indicate that should inserting `var _this;` + // without `init` at the top of the statements. + self.super_call_addresses.insert(arrow_scope_id, Address::DUMMY); + let assignment = ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + this_var.create_write_target(ctx), + ctx.ast.expression_this(SPAN), + ); + let statement = ctx.ast.statement_expression(SPAN, assignment); + self.ctx.statement_injector.insert_after(&address, statement); + } + } Some(ctx.ast.alloc(this_var.create_spanned_read_reference(span, ctx))) } @@ -703,6 +728,25 @@ impl<'a> ArrowFunctionConverter<'a> { ctx: &mut TraverseCtx<'a>, ) -> Option> { if self.super_methods.is_none() || !call.callee.is_member_expression() { + // `super()` + // Store the address in case we need to insert `var _this;` after it. + if call.callee.is_super() { + let scope_id = ctx.current_scope_id(); + self.super_call_addresses.entry(scope_id).or_insert_with(|| { + ctx.ancestors() + .find(|ancestor| { + matches!( + ancestor, + // const A = super(): + Ancestor::VariableDeclarationDeclarations(_) + // super(); + | Ancestor::ExpressionStatementExpression(_) + ) + }) + .unwrap() + .address() + }); + } return None; } @@ -1070,12 +1114,19 @@ impl<'a> ArrowFunctionConverter<'a> { // `_this = this;` if let Some(this_var) = this_var { + let init = if self.super_call_addresses.is_empty() { + Some(ctx.ast.expression_this(SPAN)) + } else { + // Clear the dummy address. + self.super_call_addresses.clear(); + None + }; Self::adjust_binding_scope(target_scope_id, &this_var, ctx); let variable_declarator = ctx.ast.variable_declarator( SPAN, VariableDeclarationKind::Var, this_var.create_binding_pattern(ctx), - Some(ctx.ast.expression_this(SPAN)), + init, false, ); declarations.push(variable_declarator); diff --git a/crates/oxc_transformer/src/common/mod.rs b/crates/oxc_transformer/src/common/mod.rs index 859a165cfb14f..c811aaa61dc17 100644 --- a/crates/oxc_transformer/src/common/mod.rs +++ b/crates/oxc_transformer/src/common/mod.rs @@ -25,7 +25,7 @@ pub struct Common<'a, 'ctx> { var_declarations: VarDeclarations<'a, 'ctx>, statement_injector: StatementInjector<'a, 'ctx>, top_level_statements: TopLevelStatements<'a, 'ctx>, - arrow_function_converter: ArrowFunctionConverter<'a>, + arrow_function_converter: ArrowFunctionConverter<'a, 'ctx>, } impl<'a, 'ctx> Common<'a, 'ctx> { @@ -35,7 +35,7 @@ impl<'a, 'ctx> Common<'a, 'ctx> { var_declarations: VarDeclarations::new(ctx), statement_injector: StatementInjector::new(ctx), top_level_statements: TopLevelStatements::new(ctx), - arrow_function_converter: ArrowFunctionConverter::new(options), + arrow_function_converter: ArrowFunctionConverter::new(options, ctx), } } } diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/options.json b/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/options.json deleted file mode 100644 index af5c06805e802..0000000000000 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/options.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": [ - [ - "transform-class-properties", - { - "loose": true - } - ] - ] -} diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/output.js b/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/output.js deleted file mode 100644 index 1f630a5394cca..0000000000000 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/output.js +++ /dev/null @@ -1,12 +0,0 @@ -var _scopedFunctionWithThis = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("scopedFunctionWithThis"); -class Child extends Parent { - constructor() { - super(); - Object.defineProperty(this, _scopedFunctionWithThis, { - writable: true, - value: () => { - this.name = {}; - } - }); - } -} diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/reason.txt b/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/reason.txt deleted file mode 100644 index 2637988f084f0..0000000000000 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/private-loose/foobar/reason.txt +++ /dev/null @@ -1,2 +0,0 @@ -Disable arrow functions transform in `options.json` because it malfunctions. -But these fixtures aren't to test arrow functions transform. diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/options.json b/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/options.json deleted file mode 100644 index af5c06805e802..0000000000000 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/options.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": [ - [ - "transform-class-properties", - { - "loose": true - } - ] - ] -} diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/output.js b/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/output.js deleted file mode 100644 index 05143dde05be1..0000000000000 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/output.js +++ /dev/null @@ -1,8 +0,0 @@ -class Child extends Parent { - constructor() { - super(); - this.scopedFunctionWithThis = () => { - this.name = {}; - }; - } -} diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/reason.txt b/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/reason.txt deleted file mode 100644 index 2637988f084f0..0000000000000 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-class-properties/test/fixtures/public-loose/foobar/reason.txt +++ /dev/null @@ -1,2 +0,0 @@ -Disable arrow functions transform in `options.json` because it malfunctions. -But these fixtures aren't to test arrow functions transform. diff --git a/tasks/transform_conformance/snapshots/oxc.snap.md b/tasks/transform_conformance/snapshots/oxc.snap.md index a0c97595264cf..eed9b6b5cf1ce 100644 --- a/tasks/transform_conformance/snapshots/oxc.snap.md +++ b/tasks/transform_conformance/snapshots/oxc.snap.md @@ -1,6 +1,6 @@ commit: 54a8389f -Passed: 114/130 +Passed: 116/132 # All Passed: * babel-plugin-transform-class-static-block @@ -34,7 +34,7 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)] -# babel-plugin-transform-async-to-generator (14/15) +# babel-plugin-transform-async-to-generator (16/17) * super/nested/input.js x Output mismatch diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super-nested/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super-nested/input.js new file mode 100644 index 0000000000000..f314b3a53699c --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super-nested/input.js @@ -0,0 +1,17 @@ +class Outer { + constructor() { + async () => { return [this, 2]; }; + + class Inner extends Outer{ + constructor() { + if (condition) { + const _super = super() + this.fn = async () => { return [this, 1]; }; + } + + super() + async () => { return [this, 2]; }; + } + } + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super-nested/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super-nested/output.js new file mode 100644 index 0000000000000..91e639a2e5532 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super-nested/output.js @@ -0,0 +1,28 @@ +class Outer { + constructor() { + var _this = this; + babelHelpers.asyncToGenerator(function* () { + return [_this, 2]; + }); + + class Inner extends Outer { + constructor() { + var _this2; + + if (condition) { + const _super = super(); + _this2 = this; + this.fn = babelHelpers.asyncToGenerator(function* () { + return [_this2, 1]; + }); + } + + super(); + _this2 = this; + babelHelpers.asyncToGenerator(function* () { + return [_this2, 2]; + }); + } + } + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super/input.js new file mode 100644 index 0000000000000..80b10f23d1c99 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super/input.js @@ -0,0 +1,11 @@ +class C extends S { + constructor() { + if (condition) { + const _super = super() + this.fn = async () => { return [this, 1]; }; + } + + super() + async () => { return [this, 2]; }; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super/output.js new file mode 100644 index 0000000000000..69cc05b67b0a2 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-async-to-generator/test/fixtures/class/this-after-super/output.js @@ -0,0 +1,18 @@ +class C extends S { + constructor() { + var _this; + if (condition) { + const _super = super(); + _this = this; + this.fn = babelHelpers.asyncToGenerator(function* () { + return [_this, 1]; + }); + } + + super(); + _this = this; + babelHelpers.asyncToGenerator(function* () { + return [_this, 2]; + }); + } +}