From b332f935f78cff9b244811f1ea566b56c15ae8e5 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Fri, 15 Oct 2021 19:04:09 -0400 Subject: [PATCH] fix #1425: super inside arrow inside lowered async --- CHANGELOG.md | 34 +++++++++++++++ internal/bundler/bundler_lower_test.go | 42 +++++++++++++++++++ .../bundler/snapshots/snapshots_lower.txt | 31 +++++++++++++- internal/js_parser/js_parser.go | 6 ++- internal/js_parser/js_parser_lower.go | 16 +++---- 5 files changed, 118 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d4e5d69b3..ff2d9ee591d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## Unreleased + +* Fix `super` inside arrow function inside lowered `async` function ([#1425](https://github.com/evanw/esbuild/issues/1425)) + + When an `async` function is transformed into a regular function for target environments that don't support `async` such as `--target=es6`, references to `super` inside that function must be transformed too since the `async`-to-regular function transformation moves the function body into a nested function, so the `super` references are no longer syntactically valid. However, this transform didn't handle an edge case and `super` references inside of an arrow function were overlooked. This release fixes this bug: + + ```js + // Original code + class Foo extends Bar { + async foo() { + return () => super.foo() + } + } + + // Old output (with --target=es6) + class Foo extends Bar { + foo() { + return __async(this, null, function* () { + return () => super.foo(); + }); + } + } + + // New output (with --target=es6) + class Foo extends Bar { + foo() { + var __super = (key) => super[key]; + return __async(this, null, function* () { + return () => __super("foo").call(this); + }); + } + } + ``` + ## 0.13.7 * Minify CSS alpha values correctly ([#1682](https://github.com/evanw/esbuild/issues/1682)) diff --git a/internal/bundler/bundler_lower_test.go b/internal/bundler/bundler_lower_test.go index 83355fb5754..2b579623158 100644 --- a/internal/bundler/bundler_lower_test.go +++ b/internal/bundler/bundler_lower_test.go @@ -920,6 +920,43 @@ obj-method.js: error: Transforming object literal extensions to the configured t }) } +func TestLowerAsyncSuperES2017NoBundle(t *testing.T) { + lower_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.js": ` + class Derived extends Base { + async test(key) { + return [ + await super.foo, + await super[key], + + await super.foo.name, + await super[key].name, + await super.foo?.name, + await super[key]?.name, + + await super.foo(1, 2), + await super[key](1, 2), + await super.foo?.(1, 2), + await super[key]?.(1, 2), + + await (() => super.foo)(), + await (() => super[key])(), + await (() => super.foo())(), + await (() => super[key]())(), + ] + } + } + `, + }, + entryPaths: []string{"/entry.js"}, + options: config.Options{ + UnsupportedJSFeatures: es(2017), + AbsOutputFile: "/out.js", + }, + }) +} + func TestLowerAsyncSuperES2016NoBundle(t *testing.T) { lower_suite.expectBundled(t, bundled{ files: map[string]string{ @@ -939,6 +976,11 @@ func TestLowerAsyncSuperES2016NoBundle(t *testing.T) { await super[key](1, 2), await super.foo?.(1, 2), await super[key]?.(1, 2), + + await (() => super.foo)(), + await (() => super[key])(), + await (() => super.foo())(), + await (() => super[key]())(), ] } } diff --git a/internal/bundler/snapshots/snapshots_lower.txt b/internal/bundler/snapshots/snapshots_lower.txt index 164c0d55626..2bf6f8a7290 100644 --- a/internal/bundler/snapshots/snapshots_lower.txt +++ b/internal/bundler/snapshots/snapshots_lower.txt @@ -109,12 +109,41 @@ class Derived extends Base { yield __super("foo").call(this, 1, 2), yield __super(key).call(this, 1, 2), yield (_c = __super("foo")) == null ? void 0 : _c.call(this, 1, 2), - yield (_d = __super(key)) == null ? void 0 : _d.call(this, 1, 2) + yield (_d = __super(key)) == null ? void 0 : _d.call(this, 1, 2), + yield (() => __super("foo"))(), + yield (() => __super(key))(), + yield (() => __super("foo").call(this))(), + yield (() => __super(key).call(this))() ]; }); } } +================================================================================ +TestLowerAsyncSuperES2017NoBundle +---------- /out.js ---------- +class Derived extends Base { + async test(key) { + var _a, _b, _c, _d; + return [ + await super.foo, + await super[key], + await super.foo.name, + await super[key].name, + await ((_a = super.foo) == null ? void 0 : _a.name), + await ((_b = super[key]) == null ? void 0 : _b.name), + await super.foo(1, 2), + await super[key](1, 2), + await ((_c = super.foo) == null ? void 0 : _c.call(this, 1, 2)), + await ((_d = super[key]) == null ? void 0 : _d.call(this, 1, 2)), + await (() => super.foo)(), + await (() => super[key])(), + await (() => super.foo())(), + await (() => super[key]())() + ]; + } +} + ================================================================================ TestLowerAsyncThis2016CommonJS ---------- /out.js ---------- diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index f29aba1eb1d..b6a338c9556 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -494,8 +494,6 @@ type fnOrArrowDataParse struct { // restored on the call stack around code that parses nested functions and // arrow expressions. type fnOrArrowDataVisit struct { - superIndexRef *js_ast.Ref - isArrow bool isAsync bool isGenerator bool @@ -513,6 +511,9 @@ type fnOrArrowDataVisit struct { // restored on the call stack around code that parses nested functions (but not // nested arrow functions). type fnOnlyDataVisit struct { + superIndexRef *js_ast.Ref + shouldLowerSuper bool + // This is a reference to the magic "arguments" variable that exists inside // functions in JavaScript. It will be non-nil inside functions and nil // otherwise. @@ -12813,6 +12814,7 @@ func (p *parser) visitFn(fn *js_ast.Fn, scopeLoc logger.Loc) { isThisNested: true, isNewTargetAllowed: true, argumentsRef: &fn.ArgumentsRef, + shouldLowerSuper: fn.IsAsync && p.options.unsupportedJSFeatures.Has(compat.AsyncAwait), } if fn.Name != nil { diff --git a/internal/js_parser/js_parser_lower.go b/internal/js_parser/js_parser_lower.go index c49f3df1b30..6a1c54f5747 100644 --- a/internal/js_parser/js_parser_lower.go +++ b/internal/js_parser/js_parser_lower.go @@ -443,12 +443,12 @@ func (p *parser) lowerFunction( returnStmt := js_ast.Stmt{Loc: bodyLoc, Data: &js_ast.SReturn{ValueOrNil: callAsync}} // Prepend the "super" index function if necessary - if p.fnOrArrowDataVisit.superIndexRef != nil { + if p.fnOnlyDataVisit.superIndexRef != nil { argRef := p.newSymbol(js_ast.SymbolOther, "key") - p.currentScope.Generated = append(p.currentScope.Generated, *p.fnOrArrowDataVisit.superIndexRef, argRef) + p.currentScope.Generated = append(p.currentScope.Generated, *p.fnOnlyDataVisit.superIndexRef, argRef) superIndexStmt := js_ast.Stmt{Loc: bodyLoc, Data: &js_ast.SLocal{ Decls: []js_ast.Decl{{ - Binding: js_ast.Binding{Loc: bodyLoc, Data: &js_ast.BIdentifier{Ref: *p.fnOrArrowDataVisit.superIndexRef}}, + Binding: js_ast.Binding{Loc: bodyLoc, Data: &js_ast.BIdentifier{Ref: *p.fnOnlyDataVisit.superIndexRef}}, ValueOrNil: js_ast.Expr{Loc: bodyLoc, Data: &js_ast.EArrow{ Args: []js_ast.Arg{{ Binding: js_ast.Binding{Loc: bodyLoc, Data: &js_ast.BIdentifier{Ref: argRef}}, @@ -2729,7 +2729,7 @@ func (p *parser) lowerTemplateLiteral(loc logger.Loc, e *js_ast.ETemplate) js_as } func (p *parser) shouldLowerSuperPropertyAccess(expr js_ast.Expr) bool { - if p.fnOrArrowDataVisit.isAsync && p.options.unsupportedJSFeatures.Has(compat.AsyncAwait) { + if p.fnOnlyDataVisit.shouldLowerSuper { _, isSuper := expr.Data.(*js_ast.ESuper) return isSuper } @@ -2737,13 +2737,13 @@ func (p *parser) shouldLowerSuperPropertyAccess(expr js_ast.Expr) bool { } func (p *parser) lowerSuperPropertyAccess(loc logger.Loc, key js_ast.Expr) js_ast.Expr { - if p.fnOrArrowDataVisit.superIndexRef == nil { + if p.fnOnlyDataVisit.superIndexRef == nil { ref := p.newSymbol(js_ast.SymbolOther, "__super") - p.fnOrArrowDataVisit.superIndexRef = &ref + p.fnOnlyDataVisit.superIndexRef = &ref } - p.recordUsage(*p.fnOrArrowDataVisit.superIndexRef) + p.recordUsage(*p.fnOnlyDataVisit.superIndexRef) return js_ast.Expr{Loc: loc, Data: &js_ast.ECall{ - Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: *p.fnOrArrowDataVisit.superIndexRef}}, + Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: *p.fnOnlyDataVisit.superIndexRef}}, Args: []js_ast.Expr{key}, }} }