fix(transformer): gate async-only rewrites by owning transform (#26326)

For targets that support async functions but not async generators, async-only converter mode still treated every async function as if the enabled transform would move its body. Plain async arrows and methods gained generated `this` and `super` bindings, and class-field arrows were wrapped unnecessarily.

Use the shared body-movement predicate for every async-only rewrite. Functions left native by the selected target now remain untouched, while async generator functions that are lowered keep their required lexical captures.

## Example

A plain async method using `super` previously gained a generated `_superprop_getValue` binding even though the method remained native. This made `eval("typeof _superprop_getValue")` return `"function"` instead of `"undefined"`. The method now remains unchanged.

Verified with focused output/runtime conformance, full transformer output conformance, and `cargo test -p oxc_transformer`.

Stacked on #26317.

AI assistance was used to diagnose the remaining transform boundaries, implement the predicate checks, and add tests. I remain responsible for reviewing and understanding the changes before merge.
This commit is contained in:
Dunqing committed 2026-09-07 07:21:47 +00:00
1 parent f45a9ee58b
commit 3d95477214
7 files changed
+123 -8

No files matched your search

@@ -233,7 +233,8 @@ impl<'a> Traverse<'a, TransformState<'a>> for ArrowFunctionConverter<'a> {
if Self::is_class_method_like_ancestor(ctx.parent()) {
self.super_methods_stack.push(FxIndexMap::default());
self.super_needs_transform_stack.push(func.r#async);
self.super_needs_transform_stack
.push(self.will_transform_async_function(func.r#async, func.generator));
}
}
@@ -289,8 +290,10 @@ impl<'a> Traverse<'a, TransformState<'a>> for ArrowFunctionConverter<'a> {
self.this_var_stack.push(None);
self.super_methods_stack.push(FxIndexMap::default());
}
self.super_needs_transform_stack
.push(arrow.r#async || *self.super_needs_transform_stack.last());
self.super_needs_transform_stack.push(
self.will_transform_async_function(arrow.r#async, false)
|| *self.super_needs_transform_stack.last(),
);
}
}
@@ -429,7 +432,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for ArrowFunctionConverter<'a> {
Expression::ArrowFunctionExpression(arrow)
// TODO: If the async arrow function without `this` or `super` usage, we can skip this step.
if self.is_async_only()
&& arrow.r#async
&& self.will_transform_async_function(arrow.r#async, false)
&& Self::in_class_property_definition_value(ctx)
=> {
// Inside class property definition value, since async arrow function will be
@@ -631,7 +634,9 @@ impl<'a> ArrowFunctionConverter<'a> {
| Ancestor::StaticBlockBody(_) => return None,
// Arrow function
Ancestor::ArrowFunctionExpressionParams(func) => {
return if self.is_async_only() && !*func.r#async() {
return if self.is_async_only()
&& !self.will_transform_async_function(*func.r#async(), false)
{
// Continue checking the parent to see if it's inside an async function.
continue;
} else {
@@ -639,7 +644,9 @@ impl<'a> ArrowFunctionConverter<'a> {
};
}
Ancestor::ArrowFunctionExpressionBody(func) => {
return if self.is_async_only() && !*func.r#async() {
return if self.is_async_only()
&& !self.will_transform_async_function(*func.r#async(), false)
{
// Continue checking the parent to see if it's inside an async function.
continue;
} else {
@@ -1,6 +1,6 @@
commit: 1eac4481
Passed: 273/401
Passed: 274/402
# All Passed:
* babel-plugin-transform-class-static-block
@@ -2,7 +2,7 @@ commit: 1eac4481
node: v26.5.0
Passed: 17 of 19 (89.47%)
Passed: 18 of 20 (90.00%)
Failures:
@@ -0,0 +1,41 @@
function outer() {
return async () => {
void this;
return eval("typeof _this");
};
}
class Base {
get value() {
return 1;
}
}
class Derived extends Base {
field = async () => {};
async direct() {
super.value;
return eval("typeof _superprop_getValue");
}
arrow() {
return async () => {
super.value;
return eval("typeof _superprop_getValue2");
};
}
async *generator() {
yield super.value;
}
}
const instance = new Derived();
return Promise.all([outer()(), instance.direct(), instance.arrow()(), instance.generator().next()]).then(function (results) {
expect(results[0]).toBe("undefined");
expect(results[1]).toBe("undefined");
expect(results[2]).toBe("undefined");
expect(results[3].value).toBe(1);
expect(instance.field.name).toBe("field");
});
@@ -0,0 +1,32 @@
function outer() {
return async () => {
void this;
return eval("typeof _this");
};
}
class Base {
get value() {
return 1;
}
}
class Derived extends Base {
field = async () => {};
async direct() {
super.value;
return eval("typeof _superprop_getValue");
}
arrow() {
return async () => {
super.value;
return eval("typeof _superprop_getValue");
};
}
async *generator() {
yield super.value;
}
}
@@ -0,0 +1,5 @@
{
"plugins": [
"transform-async-generator-functions"
]
}
@@ -0,0 +1,30 @@
function outer() {
return async () => {
void this;
return eval("typeof _this");
};
}
class Base {
get value() {
return 1;
}
}
class Derived extends Base {
field = async () => {};
async direct() {
super.value;
return eval("typeof _superprop_getValue");
}
arrow() {
return async () => {
super.value;
return eval("typeof _superprop_getValue");
};
}
generator() {
var _superprop_getValue = () => super.value;
return babelHelpers.wrapAsyncGenerator(function* () {
yield _superprop_getValue();
})();
}
}