From 5447e9dd581c24c74c6689799433fe9f2a8b78e1 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 3 Apr 2022 19:13:08 -0400 Subject: [PATCH] fix #2134: nested `super()` in class transform --- CHANGELOG.md | 46 +++++ .../bundler/snapshots/snapshots_lower.txt | 2 +- internal/js_ast/js_ast.go | 11 - internal/js_parser/js_parser.go | 69 ++++--- internal/js_parser/js_parser_lower.go | 194 ++++++++++++++++-- internal/js_parser/js_parser_test.go | 4 + internal/js_parser/ts_parser_test.go | 63 ++++++ 7 files changed, 331 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c9cd834a9..328769376e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,52 @@ With this release, esbuild can now parse these new type parameter modifiers. This feature was contributed by [@magic-akari](https://github.com/magic-akari). +* Improve support for `super()` constructor calls in nested locations ([#2134](https://github.com/evanw/esbuild/issues/2134)) + + In JavaScript, derived classes must call `super()` somewhere in the `constructor` method before being able to access `this`. Class public instance fields, class private instance fields, and TypeScript constructor parameter properties can all potentially cause code which uses `this` to be inserted into the constructor body, which must be inserted after the `super()` call. To make these insertions straightforward to implement, the TypeScript compiler doesn't allow calling `super()` somewhere other than in a root-level statement in the constructor body in these cases. + + Previously esbuild's class transformations only worked correctly when `super()` was called in a root-level statement in the constructor body, just like the TypeScript compiler. But with this release, esbuild should now generate correct code as long as the call to `super()` appears anywhere in the constructor body: + + ```ts + // Original code + class Foo extends Bar { + constructor(public skip = false) { + if (skip) { + super(null) + return + } + super({ keys: [] }) + } + } + + // Old output (incorrect) + class Foo extends Bar { + constructor(skip = false) { + if (skip) { + super(null); + return; + } + super({ keys: [] }); + this.skip = skip; + } + } + + // New output (correct) + class Foo extends Bar { + constructor(skip = false) { + var __super = (...args) => { + super(...args); + this.skip = skip; + }; + if (skip) { + __super(null); + return; + } + __super({ keys: [] }); + } + } + ``` + ## 0.14.30 * Change the context of TypeScript parameter decorators ([#2147](https://github.com/evanw/esbuild/issues/2147)) diff --git a/internal/bundler/snapshots/snapshots_lower.txt b/internal/bundler/snapshots/snapshots_lower.txt index d3cfe7f9be8..cf98d8df6d8 100644 --- a/internal/bundler/snapshots/snapshots_lower.txt +++ b/internal/bundler/snapshots/snapshots_lower.txt @@ -5,9 +5,9 @@ export class A { } export class B extends A { constructor(c) { + var _a; super(); __privateAdd(this, _e, void 0); - var _a; __privateSet(this, _e, (_a = c.d) != null ? _a : "test"); } f() { diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go index 2e4366b0d18..daa00a8dfbf 100644 --- a/internal/js_ast/js_ast.go +++ b/internal/js_ast/js_ast.go @@ -1021,17 +1021,6 @@ type SContinue struct { Label *LocRef } -func IsSuperCall(stmt Stmt) bool { - if expr, ok := stmt.Data.(*SExpr); ok { - if call, ok := expr.Value.Data.(*ECall); ok { - if _, ok := call.Target.Data.(*ESuper); ok { - return true - } - } - } - return false -} - type ClauseItem struct { Alias string diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 99ea6bd2e36..d9062b154e7 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -199,6 +199,7 @@ type parser struct { importMetaRef js_ast.Ref promiseRef js_ast.Ref runtimePublicFieldImport js_ast.Ref + superCtorRef js_ast.Ref // For lowering private methods weakMapRef js_ast.Ref @@ -7325,15 +7326,8 @@ func (p *parser) visitStmtsAndPrependTempRefs(stmts []js_ast.Stmt, opts prependT p.recordDeclaredSymbol(temp.ref) } } - - // If the first statement is a super() call, make sure it stays that way if len(decls) > 0 { - stmt := js_ast.Stmt{Data: &js_ast.SLocal{Kind: js_ast.LocalVar, Decls: decls}} - if len(stmts) > 0 && js_ast.IsSuperCall(stmts[0]) { - stmts = append([]js_ast.Stmt{stmts[0], stmt}, stmts[1:]...) - } else { - stmts = append([]js_ast.Stmt{stmt}, stmts...) - } + stmts = append([]js_ast.Stmt{{Data: &js_ast.SLocal{Kind: js_ast.LocalVar, Decls: decls}}}, stmts...) } p.tempRefsToDeclare = oldTempRefs @@ -7590,7 +7584,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt // Merge adjacent expression statements if len(result) > 0 { prevStmt := result[len(result)-1] - if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) && !js_ast.IsSuperCall(stmt) { + if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok { prevS.Value = js_ast.JoinWithComma(prevS.Value, s.Value) continue } @@ -7600,7 +7594,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt // Absorb a previous expression statement if len(result) > 0 { prevStmt := result[len(result)-1] - if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) { + if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok { s.Test = js_ast.JoinWithComma(prevS.Value, s.Test) result = result[:len(result)-1] } @@ -7610,7 +7604,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt // Absorb a previous expression statement if len(result) > 0 { prevStmt := result[len(result)-1] - if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) { + if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok { s.Test = js_ast.JoinWithComma(prevS.Value, s.Test) result = result[:len(result)-1] } @@ -7713,7 +7707,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt // Merge return statements with the previous expression statement if len(result) > 0 && s.ValueOrNil.Data != nil { prevStmt := result[len(result)-1] - if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) { + if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok { result[len(result)-1] = js_ast.Stmt{Loc: prevStmt.Loc, Data: &js_ast.SReturn{ValueOrNil: js_ast.JoinWithComma(prevS.Value, s.ValueOrNil)}} continue @@ -7726,7 +7720,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt // Merge throw statements with the previous expression statement if len(result) > 0 { prevStmt := result[len(result)-1] - if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) { + if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok { result[len(result)-1] = js_ast.Stmt{Loc: prevStmt.Loc, Data: &js_ast.SThrow{Value: js_ast.JoinWithComma(prevS.Value, s.Value)}} continue } @@ -7740,7 +7734,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt case *js_ast.SFor: if len(result) > 0 { prevStmt := result[len(result)-1] - if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) { + if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok { // Insert the previous expression into the for loop initializer if s.InitOrNil.Data == nil { result[len(result)-1] = stmt @@ -7841,11 +7835,6 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt break returnLoop } - // Do not absorb a "super()" call so that we keep it first - if js_ast.IsSuperCall(prevStmt) { - break returnLoop - } - // "a(); return b;" => "return a(), b;" lastReturn = &js_ast.SReturn{ValueOrNil: js_ast.JoinWithComma(prevS.Value, lastReturn.ValueOrNil)} @@ -7918,11 +7907,6 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt switch prevS := prevStmt.Data.(type) { case *js_ast.SExpr: - // Do not absorb a "super()" call so that we keep it first - if js_ast.IsSuperCall(prevStmt) { - break throwLoop - } - // "a(); throw b;" => "throw a(), b;" lastThrow = &js_ast.SThrow{Value: js_ast.JoinWithComma(prevS.Value, lastThrow.Value)} @@ -9020,10 +9004,10 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_ return stmts case *js_ast.SClass: - shadowRef := p.visitClass(s.Value.Loc, &s2.Class, true /* isDefaultExport */) + result := p.visitClass(s.Value.Loc, &s2.Class, true /* isDefaultExport */) // Lower class field syntax for browsers that don't support it - classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, shadowRef) + classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, result) return append(stmts, classStmts...) default: @@ -9596,7 +9580,7 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_ return stmts case *js_ast.SClass: - shadowRef := p.visitClass(stmt.Loc, &s.Class, false /* isDefaultExport */) + result := p.visitClass(stmt.Loc, &s.Class, false /* isDefaultExport */) // Remove the export flag inside a namespace wasExportInsideNamespace := s.IsExport && p.enclosingNamespaceArgRef != nil @@ -9605,7 +9589,7 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_ } // Lower class field syntax for browsers that don't support it - classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, shadowRef) + classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, result) stmts = append(stmts, classStmts...) // Handle exporting this class from a namespace @@ -10117,7 +10101,8 @@ func (p *parser) visitTSDecorators(tsDecorators []js_ast.Expr, tsDecoratorScope } type visitClassResult struct { - shadowRef js_ast.Ref + shadowRef js_ast.Ref + superCtorRef js_ast.Ref } func (p *parser) visitClass(nameScopeLoc logger.Loc, class *js_ast.Class, isDefaultExport bool) (result visitClassResult) { @@ -10185,6 +10170,18 @@ func (p *parser) visitClass(nameScopeLoc logger.Loc, class *js_ast.Class, isDefa p.validateDeclaredSymbolName(class.Name.Loc, p.symbols[class.Name.Ref.InnerIndex].OriginalName) } + // Create the "__super" symbol if necessary. This will cause us to replace + // all "super()" call expressions with a call to this symbol, which will + // then be inserted into the "constructor" method. + result.superCtorRef = js_ast.InvalidRef + if classLoweringInfo.shimSuperCtorCalls { + result.superCtorRef = p.newSymbol(js_ast.SymbolOther, "__super") + p.currentScope.Generated = append(p.currentScope.Generated, result.superCtorRef) + p.recordDeclaredSymbol(result.superCtorRef) + } + oldSuperCtorRef := p.superCtorRef + p.superCtorRef = result.superCtorRef + var classNameRef js_ast.Ref if class.Name != nil { classNameRef = class.Name.Ref @@ -10371,6 +10368,7 @@ func (p *parser) visitClass(nameScopeLoc logger.Loc, class *js_ast.Class, isDefa class.Properties = class.Properties[:end] p.enclosingClassKeyword = oldEnclosingClassKeyword + p.superCtorRef = oldSuperCtorRef p.popScope() if p.symbols[result.shadowRef.InnerIndex].UseCountEstimate == 0 { @@ -13288,6 +13286,14 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO if t.CallCanBeUnwrappedIfUnused { e.CanBeUnwrappedIfUnused = true } + + case *js_ast.ESuper: + // If we're shimming "super()" calls, replace this call with "__super()" + if p.superCtorRef != js_ast.InvalidRef { + p.recordUsage(p.superCtorRef) + target.Data = &js_ast.EIdentifier{Ref: p.superCtorRef} + e.Target.Data = target.Data + } } // Handle parenthesized optional chains @@ -13502,10 +13508,10 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO } case *js_ast.EClass: - shadowRef := p.visitClass(expr.Loc, &e.Class, false /* isDefaultExport */) + result := p.visitClass(expr.Loc, &e.Class, false /* isDefaultExport */) // Lower class field syntax for browsers that don't support it - _, expr = p.lowerClass(js_ast.Stmt{}, expr, shadowRef) + _, expr = p.lowerClass(js_ast.Stmt{}, expr, result) default: // Note: EPrivateIdentifier and EMangledProperty should have already been handled @@ -14729,6 +14735,7 @@ func newParser(log logger.Log, source logger.Source, lexer js_lexer.Lexer, optio afterArrowBodyLoc: logger.Loc{Start: -1}, importMetaRef: js_ast.InvalidRef, runtimePublicFieldImport: js_ast.InvalidRef, + superCtorRef: js_ast.InvalidRef, // For lowering private methods weakMapRef: js_ast.InvalidRef, diff --git a/internal/js_parser/js_parser_lower.go b/internal/js_parser/js_parser_lower.go index ff132f63dd0..129c2203f2d 100644 --- a/internal/js_parser/js_parser_lower.go +++ b/internal/js_parser/js_parser_lower.go @@ -1788,6 +1788,7 @@ type classLoweringInfo struct { avoidTDZ bool lowerAllInstanceFields bool lowerAllStaticFields bool + shimSuperCtorCalls bool } func (p *parser) computeClassLoweringInfo(class *js_ast.Class) (result classLoweringInfo) { @@ -1933,6 +1934,21 @@ func (p *parser) computeClassLoweringInfo(class *js_ast.Class) (result classLowe // In that case the initializer of "bar" would fail to call "#foo" because // it's only added to the instance in the body of the constructor. if prop.IsMethod { + // We need to shim "super()" inside the constructor if this is a derived + // class and the constructor has any parameter properties, since those + // use "this" and we can only access "this" after "super()" is called + if class.ExtendsOrNil.Data != nil { + if key, ok := prop.Key.Data.(*js_ast.EString); ok && helpers.UTF16EqualsString(key.Value, "constructor") { + if fn, ok := prop.ValueOrNil.Data.(*js_ast.EFunction); ok { + for _, arg := range fn.Fn.Args { + if arg.IsTypeScriptCtorField { + result.shimSuperCtorCalls = true + break + } + } + } + } + } continue } @@ -1984,6 +2000,13 @@ func (p *parser) computeClassLoweringInfo(class *js_ast.Class) (result classLowe } } + // We need to shim "super()" inside the constructor if this is a derived + // class and there are any instance fields that need to be lowered, since + // those use "this" and we can only access "this" after "super()" is called + if result.lowerAllInstanceFields && class.ExtendsOrNil.Data != nil { + result.shimSuperCtorCalls = true + } + return } @@ -2493,29 +2516,27 @@ func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, result visitClas // Make sure the constructor has a super() call if needed if class.ExtendsOrNil.Data != nil { + target := js_ast.Expr{Loc: classLoc, Data: js_ast.ESuperShared} + if classLoweringInfo.shimSuperCtorCalls { + p.recordUsage(result.superCtorRef) + target.Data = &js_ast.EIdentifier{Ref: result.superCtorRef} + } argumentsRef := p.newSymbol(js_ast.SymbolUnbound, "arguments") p.currentScope.Generated = append(p.currentScope.Generated, argumentsRef) ctor.Fn.Body.Block.Stmts = append(ctor.Fn.Body.Block.Stmts, js_ast.Stmt{Loc: classLoc, Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: classLoc, Data: &js_ast.ECall{ - Target: js_ast.Expr{Loc: classLoc, Data: js_ast.ESuperShared}, + Target: target, Args: []js_ast.Expr{{Loc: classLoc, Data: &js_ast.ESpread{Value: js_ast.Expr{Loc: classLoc, Data: &js_ast.EIdentifier{Ref: argumentsRef}}}}}, }}}}) } } - // Insert the instance field initializers after the super call if there is one - stmtsFrom := ctor.Fn.Body.Block.Stmts - stmtsTo := []js_ast.Stmt{} - for i, stmt := range stmtsFrom { - if js_ast.IsSuperCall(stmt) { - stmtsTo = append(stmtsTo, stmtsFrom[0:i+1]...) - stmtsFrom = stmtsFrom[i+1:] - break - } - } - stmtsTo = append(stmtsTo, parameterFields...) - stmtsTo = append(stmtsTo, instancePrivateMethods...) - stmtsTo = append(stmtsTo, instanceMembers...) - ctor.Fn.Body.Block.Stmts = append(stmtsTo, stmtsFrom...) + // Make sure the instance field initializers come after "super()" since + // they need "this" to ba available + generatedStmts := make([]js_ast.Stmt, 0, len(parameterFields)+len(instancePrivateMethods)+len(instanceMembers)) + generatedStmts = append(generatedStmts, parameterFields...) + generatedStmts = append(generatedStmts, instancePrivateMethods...) + generatedStmts = append(generatedStmts, instanceMembers...) + p.insertStmtsAfterSuperCall(&ctor.Fn.Body, generatedStmts, result.superCtorRef) // Sort the constructor first to match the TypeScript compiler's output for i := 0; i < len(class.Properties); i++ { @@ -2741,6 +2762,149 @@ func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, result visitClas return stmts, js_ast.Expr{} } +// Replace "super()" calls with our shim so that we can guarantee +// that instance field initialization doesn't happen before "super()" +// is called, since at that point "this" isn't available. +func (p *parser) insertStmtsAfterSuperCall(body *js_ast.FnBody, stmtsToInsert []js_ast.Stmt, superCtorRef js_ast.Ref) { + // If this class has no base class, then there's no "super()" call to handle + if superCtorRef == js_ast.InvalidRef { + body.Block.Stmts = append(stmtsToInsert, body.Block.Stmts...) + return + } + + // It's likely that there's only one "super()" call, and that it's a + // top-level expression in the constructor function body. If so, we + // can generate tighter code for this common case. + if p.symbols[superCtorRef.InnerIndex].UseCountEstimate == 1 { + for i, stmt := range body.Block.Stmts { + var before js_ast.Expr + var callLoc logger.Loc + var callData *js_ast.ECall + var after js_ast.Stmt + + switch s := stmt.Data.(type) { + case *js_ast.SExpr: + if b, loc, c, a := findFirstTopLevelSuperCall(s.Value, superCtorRef); c != nil { + before, callLoc, callData = b, loc, c + if a.Data != nil { + s.Value = a + after = js_ast.Stmt{Loc: a.Loc, Data: s} + } + } + + case *js_ast.SReturn: + if s.ValueOrNil.Data != nil { + if b, loc, c, a := findFirstTopLevelSuperCall(s.ValueOrNil, superCtorRef); c != nil && a.Data != nil { + before, callLoc, callData = b, loc, c + s.ValueOrNil = a + after = js_ast.Stmt{Loc: a.Loc, Data: s} + } + } + + case *js_ast.SThrow: + if b, loc, c, a := findFirstTopLevelSuperCall(s.Value, superCtorRef); c != nil && a.Data != nil { + before, callLoc, callData = b, loc, c + s.Value = a + after = js_ast.Stmt{Loc: a.Loc, Data: s} + } + + case *js_ast.SIf: + if b, loc, c, a := findFirstTopLevelSuperCall(s.Test, superCtorRef); c != nil && a.Data != nil { + before, callLoc, callData = b, loc, c + s.Test = a + after = js_ast.Stmt{Loc: a.Loc, Data: s} + } + + case *js_ast.SSwitch: + if b, loc, c, a := findFirstTopLevelSuperCall(s.Test, superCtorRef); c != nil && a.Data != nil { + before, callLoc, callData = b, loc, c + s.Test = a + after = js_ast.Stmt{Loc: a.Loc, Data: s} + } + + case *js_ast.SFor: + if expr, ok := s.InitOrNil.Data.(*js_ast.SExpr); ok { + if b, loc, c, a := findFirstTopLevelSuperCall(expr.Value, superCtorRef); c != nil { + before, callLoc, callData = b, loc, c + if a.Data != nil { + expr.Value = a + } else { + s.InitOrNil.Data = nil + } + after = js_ast.Stmt{Loc: a.Loc, Data: s} + } + } + } + + if callData != nil { + // Revert "__super()" back to "super()" + callData.Target.Data = js_ast.ESuperShared + p.ignoreUsage(superCtorRef) + + // Inject "stmtsToInsert" after "super()" + stmtsBefore := body.Block.Stmts[:i] + stmtsAfter := body.Block.Stmts[i+1:] + stmts := append([]js_ast.Stmt{}, stmtsBefore...) + if before.Data != nil { + stmts = append(stmts, js_ast.Stmt{Loc: before.Loc, Data: &js_ast.SExpr{Value: before}}) + } + stmts = append(stmts, js_ast.Stmt{Loc: callLoc, Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: callLoc, Data: callData}}}) + stmts = append(stmts, stmtsToInsert...) + if after.Data != nil { + stmts = append(stmts, after) + } + stmts = append(stmts, stmtsAfter...) + body.Block.Stmts = stmts + return + } + } + } + + // Otherwise, inject a generated "__super" helper function at the top of the + // constructor that looks like this: + // + // var __super = (...args) => { + // super(...args); + // ...stmtsToInsert... + // }; + // + argsRef := p.newSymbol(js_ast.SymbolOther, "args") + p.currentScope.Generated = append(p.currentScope.Generated, argsRef) + stmtsToInsert = append([]js_ast.Stmt{{Loc: body.Loc, Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: body.Loc, Data: &js_ast.ECall{ + Target: js_ast.Expr{Loc: body.Loc, Data: js_ast.ESuperShared}, + Args: []js_ast.Expr{{Loc: body.Loc, Data: &js_ast.ESpread{Value: js_ast.Expr{Loc: body.Loc, Data: &js_ast.EIdentifier{Ref: argsRef}}}}}, + }}}}}, stmtsToInsert...) + body.Block.Stmts = append([]js_ast.Stmt{{Loc: body.Loc, Data: &js_ast.SLocal{Decls: []js_ast.Decl{{ + Binding: js_ast.Binding{Loc: body.Loc, Data: &js_ast.BIdentifier{Ref: superCtorRef}}, ValueOrNil: js_ast.Expr{Loc: body.Loc, Data: &js_ast.EArrow{ + HasRestArg: true, + Args: []js_ast.Arg{{Binding: js_ast.Binding{Loc: body.Loc, Data: &js_ast.BIdentifier{Ref: argsRef}}}}, + Body: js_ast.FnBody{Loc: body.Loc, Block: js_ast.SBlock{Stmts: stmtsToInsert}}, + }}, + }}}}}, body.Block.Stmts...) +} + +func findFirstTopLevelSuperCall(expr js_ast.Expr, superCtorRef js_ast.Ref) (js_ast.Expr, logger.Loc, *js_ast.ECall, js_ast.Expr) { + if call, ok := expr.Data.(*js_ast.ECall); ok { + if target, ok := call.Target.Data.(*js_ast.EIdentifier); ok && target.Ref == superCtorRef { + call.Target.Data = js_ast.ESuperShared + return js_ast.Expr{}, expr.Loc, call, js_ast.Expr{} + } + } + + // Also search down comma operator chains for a super call + if comma, ok := expr.Data.(*js_ast.EBinary); ok && comma.Op == js_ast.BinOpComma { + if before, loc, call, after := findFirstTopLevelSuperCall(comma.Left, superCtorRef); call != nil { + return before, loc, call, js_ast.JoinWithComma(after, comma.Right) + } + + if before, loc, call, after := findFirstTopLevelSuperCall(comma.Right, superCtorRef); call != nil { + return js_ast.JoinWithComma(comma.Left, before), loc, call, after + } + } + + return js_ast.Expr{}, logger.Loc{}, nil, js_ast.Expr{} +} + func (p *parser) lowerTemplateLiteral(loc logger.Loc, e *js_ast.ETemplate) js_ast.Expr { // If there is no tag, turn this into normal string concatenation if e.TagOrNil.Data == nil { diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index b25cb0c5299..073c43353d0 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -1584,6 +1584,10 @@ func TestSuperCall(t *testing.T) { "class A extends B {\n constructor() {\n super();\n __publicField(this, \"x\", 1);\n return c;\n }\n}\n") expectPrintedMangleTarget(t, 2015, "class A extends B { x = 1; constructor() { super(); throw c } }", "class A extends B {\n constructor() {\n super();\n __publicField(this, \"x\", 1);\n throw c;\n }\n}\n") + expectPrintedMangleTarget(t, 2015, "class A extends B { x = 1; constructor() { if (true) super(1); else super(2); } }", + "class A extends B {\n constructor() {\n super(1);\n __publicField(this, \"x\", 1);\n }\n}\n") + expectPrintedMangleTarget(t, 2015, "class A extends B { x = 1; constructor() { if (foo) super(1); else super(2); } }", + "class A extends B {\n constructor() {\n var __super = (...args) => {\n super(...args);\n __publicField(this, \"x\", 1);\n };\n foo ? __super(1) : __super(2);\n }\n}\n") } func TestSuperProp(t *testing.T) { diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index dff839a2857..87df0a2fe4b 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -1717,6 +1717,69 @@ func TestTSArrow(t *testing.T) { ": ERROR: Transforming default arguments to the configured target environment is not supported yet\n") } +func TestTSSuperCall(t *testing.T) { + expectPrintedTS(t, "class A extends B { constructor(public x = 1) { foo(); super(1); } }", + `class A extends B { + constructor(x = 1) { + foo(); + super(1); + this.x = x; + } +} +`) + + expectPrintedTS(t, "class A extends B { constructor(public x = 1) { foo(); super(1); super(2); } }", + `class A extends B { + constructor(x = 1) { + var __super = (...args) => { + super(...args); + this.x = x; + }; + foo(); + __super(1); + __super(2); + } +} +`) + + expectPrintedTS(t, "class A extends B { constructor(public x = 1) { if (false) super(1); super(2); } }", `class A extends B { + constructor(x = 1) { + if (false) + __super(1); + super(2); + this.x = x; + } +} +`) + + expectPrintedTS(t, "class A extends B { constructor(public x = 1) { if (foo) super(1); super(2); } }", `class A extends B { + constructor(x = 1) { + var __super = (...args) => { + super(...args); + this.x = x; + }; + if (foo) + __super(1); + __super(2); + } +} +`) + + expectPrintedTS(t, "class A extends B { constructor(public x = 1) { if (foo) super(1); else super(2); } }", `class A extends B { + constructor(x = 1) { + var __super = (...args) => { + super(...args); + this.x = x; + }; + if (foo) + __super(1); + else + __super(2); + } +} +`) +} + func TestTSCall(t *testing.T) { expectPrintedTS(t, "foo()", "foo();\n") expectPrintedTS(t, "foo()", "foo();\n")