Skip to content

Commit 5447e9d

Browse files
committed
fix #2134: nested super() in class transform
1 parent 9f0b45f commit 5447e9d

File tree

7 files changed

+331
-58
lines changed

7 files changed

+331
-58
lines changed

CHANGELOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,52 @@
1515

1616
With this release, esbuild can now parse these new type parameter modifiers. This feature was contributed by [@magic-akari](https://github.com/magic-akari).
1717

18+
* Improve support for `super()` constructor calls in nested locations ([#2134](https://github.com/evanw/esbuild/issues/2134))
19+
20+
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.
21+
22+
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:
23+
24+
```ts
25+
// Original code
26+
class Foo extends Bar {
27+
constructor(public skip = false) {
28+
if (skip) {
29+
super(null)
30+
return
31+
}
32+
super({ keys: [] })
33+
}
34+
}
35+
36+
// Old output (incorrect)
37+
class Foo extends Bar {
38+
constructor(skip = false) {
39+
if (skip) {
40+
super(null);
41+
return;
42+
}
43+
super({ keys: [] });
44+
this.skip = skip;
45+
}
46+
}
47+
48+
// New output (correct)
49+
class Foo extends Bar {
50+
constructor(skip = false) {
51+
var __super = (...args) => {
52+
super(...args);
53+
this.skip = skip;
54+
};
55+
if (skip) {
56+
__super(null);
57+
return;
58+
}
59+
__super({ keys: [] });
60+
}
61+
}
62+
```
63+
1864
## 0.14.30
1965

2066
* Change the context of TypeScript parameter decorators ([#2147](https://github.com/evanw/esbuild/issues/2147))

internal/bundler/snapshots/snapshots_lower.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ export class A {
55
}
66
export class B extends A {
77
constructor(c) {
8+
var _a;
89
super();
910
__privateAdd(this, _e, void 0);
10-
var _a;
1111
__privateSet(this, _e, (_a = c.d) != null ? _a : "test");
1212
}
1313
f() {

internal/js_ast/js_ast.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,17 +1021,6 @@ type SContinue struct {
10211021
Label *LocRef
10221022
}
10231023

1024-
func IsSuperCall(stmt Stmt) bool {
1025-
if expr, ok := stmt.Data.(*SExpr); ok {
1026-
if call, ok := expr.Value.Data.(*ECall); ok {
1027-
if _, ok := call.Target.Data.(*ESuper); ok {
1028-
return true
1029-
}
1030-
}
1031-
}
1032-
return false
1033-
}
1034-
10351024
type ClauseItem struct {
10361025
Alias string
10371026

internal/js_parser/js_parser.go

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ type parser struct {
199199
importMetaRef js_ast.Ref
200200
promiseRef js_ast.Ref
201201
runtimePublicFieldImport js_ast.Ref
202+
superCtorRef js_ast.Ref
202203

203204
// For lowering private methods
204205
weakMapRef js_ast.Ref
@@ -7325,15 +7326,8 @@ func (p *parser) visitStmtsAndPrependTempRefs(stmts []js_ast.Stmt, opts prependT
73257326
p.recordDeclaredSymbol(temp.ref)
73267327
}
73277328
}
7328-
7329-
// If the first statement is a super() call, make sure it stays that way
73307329
if len(decls) > 0 {
7331-
stmt := js_ast.Stmt{Data: &js_ast.SLocal{Kind: js_ast.LocalVar, Decls: decls}}
7332-
if len(stmts) > 0 && js_ast.IsSuperCall(stmts[0]) {
7333-
stmts = append([]js_ast.Stmt{stmts[0], stmt}, stmts[1:]...)
7334-
} else {
7335-
stmts = append([]js_ast.Stmt{stmt}, stmts...)
7336-
}
7330+
stmts = append([]js_ast.Stmt{{Data: &js_ast.SLocal{Kind: js_ast.LocalVar, Decls: decls}}}, stmts...)
73377331
}
73387332

73397333
p.tempRefsToDeclare = oldTempRefs
@@ -7590,7 +7584,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
75907584
// Merge adjacent expression statements
75917585
if len(result) > 0 {
75927586
prevStmt := result[len(result)-1]
7593-
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) && !js_ast.IsSuperCall(stmt) {
7587+
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok {
75947588
prevS.Value = js_ast.JoinWithComma(prevS.Value, s.Value)
75957589
continue
75967590
}
@@ -7600,7 +7594,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
76007594
// Absorb a previous expression statement
76017595
if len(result) > 0 {
76027596
prevStmt := result[len(result)-1]
7603-
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) {
7597+
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok {
76047598
s.Test = js_ast.JoinWithComma(prevS.Value, s.Test)
76057599
result = result[:len(result)-1]
76067600
}
@@ -7610,7 +7604,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
76107604
// Absorb a previous expression statement
76117605
if len(result) > 0 {
76127606
prevStmt := result[len(result)-1]
7613-
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) {
7607+
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok {
76147608
s.Test = js_ast.JoinWithComma(prevS.Value, s.Test)
76157609
result = result[:len(result)-1]
76167610
}
@@ -7713,7 +7707,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
77137707
// Merge return statements with the previous expression statement
77147708
if len(result) > 0 && s.ValueOrNil.Data != nil {
77157709
prevStmt := result[len(result)-1]
7716-
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) {
7710+
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok {
77177711
result[len(result)-1] = js_ast.Stmt{Loc: prevStmt.Loc,
77187712
Data: &js_ast.SReturn{ValueOrNil: js_ast.JoinWithComma(prevS.Value, s.ValueOrNil)}}
77197713
continue
@@ -7726,7 +7720,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
77267720
// Merge throw statements with the previous expression statement
77277721
if len(result) > 0 {
77287722
prevStmt := result[len(result)-1]
7729-
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) {
7723+
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok {
77307724
result[len(result)-1] = js_ast.Stmt{Loc: prevStmt.Loc, Data: &js_ast.SThrow{Value: js_ast.JoinWithComma(prevS.Value, s.Value)}}
77317725
continue
77327726
}
@@ -7740,7 +7734,7 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
77407734
case *js_ast.SFor:
77417735
if len(result) > 0 {
77427736
prevStmt := result[len(result)-1]
7743-
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) {
7737+
if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok {
77447738
// Insert the previous expression into the for loop initializer
77457739
if s.InitOrNil.Data == nil {
77467740
result[len(result)-1] = stmt
@@ -7841,11 +7835,6 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
78417835
break returnLoop
78427836
}
78437837

7844-
// Do not absorb a "super()" call so that we keep it first
7845-
if js_ast.IsSuperCall(prevStmt) {
7846-
break returnLoop
7847-
}
7848-
78497838
// "a(); return b;" => "return a(), b;"
78507839
lastReturn = &js_ast.SReturn{ValueOrNil: js_ast.JoinWithComma(prevS.Value, lastReturn.ValueOrNil)}
78517840

@@ -7918,11 +7907,6 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt
79187907

79197908
switch prevS := prevStmt.Data.(type) {
79207909
case *js_ast.SExpr:
7921-
// Do not absorb a "super()" call so that we keep it first
7922-
if js_ast.IsSuperCall(prevStmt) {
7923-
break throwLoop
7924-
}
7925-
79267910
// "a(); throw b;" => "throw a(), b;"
79277911
lastThrow = &js_ast.SThrow{Value: js_ast.JoinWithComma(prevS.Value, lastThrow.Value)}
79287912

@@ -9020,10 +9004,10 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
90209004
return stmts
90219005

90229006
case *js_ast.SClass:
9023-
shadowRef := p.visitClass(s.Value.Loc, &s2.Class, true /* isDefaultExport */)
9007+
result := p.visitClass(s.Value.Loc, &s2.Class, true /* isDefaultExport */)
90249008

90259009
// Lower class field syntax for browsers that don't support it
9026-
classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, shadowRef)
9010+
classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, result)
90279011
return append(stmts, classStmts...)
90289012

90299013
default:
@@ -9596,7 +9580,7 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
95969580
return stmts
95979581

95989582
case *js_ast.SClass:
9599-
shadowRef := p.visitClass(stmt.Loc, &s.Class, false /* isDefaultExport */)
9583+
result := p.visitClass(stmt.Loc, &s.Class, false /* isDefaultExport */)
96009584

96019585
// Remove the export flag inside a namespace
96029586
wasExportInsideNamespace := s.IsExport && p.enclosingNamespaceArgRef != nil
@@ -9605,7 +9589,7 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
96059589
}
96069590

96079591
// Lower class field syntax for browsers that don't support it
9608-
classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, shadowRef)
9592+
classStmts, _ := p.lowerClass(stmt, js_ast.Expr{}, result)
96099593
stmts = append(stmts, classStmts...)
96109594

96119595
// Handle exporting this class from a namespace
@@ -10117,7 +10101,8 @@ func (p *parser) visitTSDecorators(tsDecorators []js_ast.Expr, tsDecoratorScope
1011710101
}
1011810102

1011910103
type visitClassResult struct {
10120-
shadowRef js_ast.Ref
10104+
shadowRef js_ast.Ref
10105+
superCtorRef js_ast.Ref
1012110106
}
1012210107

1012310108
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
1018510170
p.validateDeclaredSymbolName(class.Name.Loc, p.symbols[class.Name.Ref.InnerIndex].OriginalName)
1018610171
}
1018710172

10173+
// Create the "__super" symbol if necessary. This will cause us to replace
10174+
// all "super()" call expressions with a call to this symbol, which will
10175+
// then be inserted into the "constructor" method.
10176+
result.superCtorRef = js_ast.InvalidRef
10177+
if classLoweringInfo.shimSuperCtorCalls {
10178+
result.superCtorRef = p.newSymbol(js_ast.SymbolOther, "__super")
10179+
p.currentScope.Generated = append(p.currentScope.Generated, result.superCtorRef)
10180+
p.recordDeclaredSymbol(result.superCtorRef)
10181+
}
10182+
oldSuperCtorRef := p.superCtorRef
10183+
p.superCtorRef = result.superCtorRef
10184+
1018810185
var classNameRef js_ast.Ref
1018910186
if class.Name != nil {
1019010187
classNameRef = class.Name.Ref
@@ -10371,6 +10368,7 @@ func (p *parser) visitClass(nameScopeLoc logger.Loc, class *js_ast.Class, isDefa
1037110368
class.Properties = class.Properties[:end]
1037210369

1037310370
p.enclosingClassKeyword = oldEnclosingClassKeyword
10371+
p.superCtorRef = oldSuperCtorRef
1037410372
p.popScope()
1037510373

1037610374
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
1328813286
if t.CallCanBeUnwrappedIfUnused {
1328913287
e.CanBeUnwrappedIfUnused = true
1329013288
}
13289+
13290+
case *js_ast.ESuper:
13291+
// If we're shimming "super()" calls, replace this call with "__super()"
13292+
if p.superCtorRef != js_ast.InvalidRef {
13293+
p.recordUsage(p.superCtorRef)
13294+
target.Data = &js_ast.EIdentifier{Ref: p.superCtorRef}
13295+
e.Target.Data = target.Data
13296+
}
1329113297
}
1329213298

1329313299
// Handle parenthesized optional chains
@@ -13502,10 +13508,10 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
1350213508
}
1350313509

1350413510
case *js_ast.EClass:
13505-
shadowRef := p.visitClass(expr.Loc, &e.Class, false /* isDefaultExport */)
13511+
result := p.visitClass(expr.Loc, &e.Class, false /* isDefaultExport */)
1350613512

1350713513
// Lower class field syntax for browsers that don't support it
13508-
_, expr = p.lowerClass(js_ast.Stmt{}, expr, shadowRef)
13514+
_, expr = p.lowerClass(js_ast.Stmt{}, expr, result)
1350913515

1351013516
default:
1351113517
// 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
1472914735
afterArrowBodyLoc: logger.Loc{Start: -1},
1473014736
importMetaRef: js_ast.InvalidRef,
1473114737
runtimePublicFieldImport: js_ast.InvalidRef,
14738+
superCtorRef: js_ast.InvalidRef,
1473214739

1473314740
// For lowering private methods
1473414741
weakMapRef: js_ast.InvalidRef,

0 commit comments

Comments
 (0)