mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
2601 lines
97 KiB
Go
2601 lines
97 KiB
Go
package js_parser
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/evanw/esbuild/internal/ast"
|
|
"github.com/evanw/esbuild/internal/compat"
|
|
"github.com/evanw/esbuild/internal/config"
|
|
"github.com/evanw/esbuild/internal/helpers"
|
|
"github.com/evanw/esbuild/internal/js_ast"
|
|
"github.com/evanw/esbuild/internal/logger"
|
|
)
|
|
|
|
func (p *parser) privateSymbolNeedsToBeLowered(private *js_ast.EPrivateIdentifier) bool {
|
|
symbol := &p.symbols[private.Ref.InnerIndex]
|
|
return p.options.unsupportedJSFeatures.Has(compat.SymbolFeature(symbol.Kind)) || symbol.Flags.Has(ast.PrivateSymbolMustBeLowered)
|
|
}
|
|
|
|
func (p *parser) lowerPrivateBrandCheck(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier) js_ast.Expr {
|
|
// "#field in this" => "__privateIn(#field, this)"
|
|
return p.callRuntime(loc, "__privateIn", []js_ast.Expr{
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
target,
|
|
})
|
|
}
|
|
|
|
func (p *parser) lowerPrivateGet(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier) js_ast.Expr {
|
|
switch p.symbols[private.Ref.InnerIndex].Kind {
|
|
case ast.SymbolPrivateMethod, ast.SymbolPrivateStaticMethod:
|
|
// "this.#method" => "__privateMethod(this, #method, method_fn)"
|
|
fnRef := p.privateGetters[private.Ref]
|
|
p.recordUsage(fnRef)
|
|
return p.callRuntime(target.Loc, "__privateMethod", []js_ast.Expr{
|
|
target,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: fnRef}},
|
|
})
|
|
|
|
case ast.SymbolPrivateGet, ast.SymbolPrivateStaticGet,
|
|
ast.SymbolPrivateGetSetPair, ast.SymbolPrivateStaticGetSetPair:
|
|
// "this.#getter" => "__privateGet(this, #getter, getter_get)"
|
|
fnRef := p.privateGetters[private.Ref]
|
|
p.recordUsage(fnRef)
|
|
return p.callRuntime(target.Loc, "__privateGet", []js_ast.Expr{
|
|
target,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: fnRef}},
|
|
})
|
|
|
|
default:
|
|
// "this.#field" => "__privateGet(this, #field)"
|
|
return p.callRuntime(target.Loc, "__privateGet", []js_ast.Expr{
|
|
target,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
})
|
|
}
|
|
}
|
|
|
|
func (p *parser) lowerPrivateSet(
|
|
target js_ast.Expr,
|
|
loc logger.Loc,
|
|
private *js_ast.EPrivateIdentifier,
|
|
value js_ast.Expr,
|
|
) js_ast.Expr {
|
|
switch p.symbols[private.Ref.InnerIndex].Kind {
|
|
case ast.SymbolPrivateSet, ast.SymbolPrivateStaticSet,
|
|
ast.SymbolPrivateGetSetPair, ast.SymbolPrivateStaticGetSetPair:
|
|
// "this.#setter = 123" => "__privateSet(this, #setter, 123, setter_set)"
|
|
fnRef := p.privateSetters[private.Ref]
|
|
p.recordUsage(fnRef)
|
|
return p.callRuntime(target.Loc, "__privateSet", []js_ast.Expr{
|
|
target,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
value,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: fnRef}},
|
|
})
|
|
|
|
default:
|
|
// "this.#field = 123" => "__privateSet(this, #field, 123)"
|
|
return p.callRuntime(target.Loc, "__privateSet", []js_ast.Expr{
|
|
target,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
value,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (p *parser) lowerPrivateSetUnOp(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier, op js_ast.OpCode) js_ast.Expr {
|
|
kind := p.symbols[private.Ref.InnerIndex].Kind
|
|
|
|
// Determine the setter, if any
|
|
var setter js_ast.Expr
|
|
switch kind {
|
|
case ast.SymbolPrivateSet, ast.SymbolPrivateStaticSet,
|
|
ast.SymbolPrivateGetSetPair, ast.SymbolPrivateStaticGetSetPair:
|
|
ref := p.privateSetters[private.Ref]
|
|
p.recordUsage(ref)
|
|
setter = js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
}
|
|
|
|
// Determine the getter, if any
|
|
var getter js_ast.Expr
|
|
switch kind {
|
|
case ast.SymbolPrivateGet, ast.SymbolPrivateStaticGet,
|
|
ast.SymbolPrivateGetSetPair, ast.SymbolPrivateStaticGetSetPair:
|
|
ref := p.privateGetters[private.Ref]
|
|
p.recordUsage(ref)
|
|
getter = js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
}
|
|
|
|
// Only include necessary arguments
|
|
args := []js_ast.Expr{
|
|
target,
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
}
|
|
if setter.Data != nil {
|
|
args = append(args, setter)
|
|
}
|
|
if getter.Data != nil {
|
|
if setter.Data == nil {
|
|
args = append(args, js_ast.Expr{Loc: loc, Data: js_ast.ENullShared})
|
|
}
|
|
args = append(args, getter)
|
|
}
|
|
|
|
// "target.#private++" => "__privateWrapper(target, #private, private_set, private_get)._++"
|
|
return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{
|
|
Op: op,
|
|
Value: js_ast.Expr{Loc: target.Loc, Data: &js_ast.EDot{
|
|
Target: p.callRuntime(target.Loc, "__privateWrapper", args),
|
|
NameLoc: target.Loc,
|
|
Name: "_",
|
|
}},
|
|
}}
|
|
}
|
|
|
|
func (p *parser) lowerPrivateSetBinOp(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier, op js_ast.OpCode, value js_ast.Expr) js_ast.Expr {
|
|
// "target.#private += 123" => "__privateSet(target, #private, __privateGet(target, #private) + 123)"
|
|
targetFunc, targetWrapFunc := p.captureValueWithPossibleSideEffects(target.Loc, 2, target, valueDefinitelyNotMutated)
|
|
return targetWrapFunc(p.lowerPrivateSet(targetFunc(), loc, private, js_ast.Expr{Loc: value.Loc, Data: &js_ast.EBinary{
|
|
Op: op,
|
|
Left: p.lowerPrivateGet(targetFunc(), loc, private),
|
|
Right: value,
|
|
}}))
|
|
}
|
|
|
|
// Returns valid data if target is an expression of the form "foo.#bar" and if
|
|
// the language target is such that private members must be lowered
|
|
func (p *parser) extractPrivateIndex(target js_ast.Expr) (js_ast.Expr, logger.Loc, *js_ast.EPrivateIdentifier) {
|
|
if index, ok := target.Data.(*js_ast.EIndex); ok {
|
|
if private, ok := index.Index.Data.(*js_ast.EPrivateIdentifier); ok && p.privateSymbolNeedsToBeLowered(private) {
|
|
return index.Target, index.Index.Loc, private
|
|
}
|
|
}
|
|
return js_ast.Expr{}, logger.Loc{}, nil
|
|
}
|
|
|
|
// Returns a valid property if target is an expression of the form "super.bar"
|
|
// or "super[bar]" and if the situation is such that it must be lowered
|
|
func (p *parser) extractSuperProperty(target js_ast.Expr) js_ast.Expr {
|
|
switch e := target.Data.(type) {
|
|
case *js_ast.EDot:
|
|
if p.shouldLowerSuperPropertyAccess(e.Target) {
|
|
return js_ast.Expr{Loc: e.NameLoc, Data: &js_ast.EString{Value: helpers.StringToUTF16(e.Name)}}
|
|
}
|
|
case *js_ast.EIndex:
|
|
if p.shouldLowerSuperPropertyAccess(e.Target) {
|
|
return e.Index
|
|
}
|
|
}
|
|
return js_ast.Expr{}
|
|
}
|
|
|
|
func (p *parser) lowerSuperPropertyOrPrivateInAssign(expr js_ast.Expr) (js_ast.Expr, bool) {
|
|
didLower := false
|
|
|
|
switch e := expr.Data.(type) {
|
|
case *js_ast.ESpread:
|
|
if value, ok := p.lowerSuperPropertyOrPrivateInAssign(e.Value); ok {
|
|
e.Value = value
|
|
didLower = true
|
|
}
|
|
|
|
case *js_ast.EDot:
|
|
// "[super.foo] = [bar]" => "[__superWrapper(this, 'foo')._] = [bar]"
|
|
if p.shouldLowerSuperPropertyAccess(e.Target) {
|
|
key := js_ast.Expr{Loc: e.NameLoc, Data: &js_ast.EString{Value: helpers.StringToUTF16(e.Name)}}
|
|
expr = p.callSuperPropertyWrapper(expr.Loc, key)
|
|
didLower = true
|
|
}
|
|
|
|
case *js_ast.EIndex:
|
|
// "[super[foo]] = [bar]" => "[__superWrapper(this, foo)._] = [bar]"
|
|
if p.shouldLowerSuperPropertyAccess(e.Target) {
|
|
expr = p.callSuperPropertyWrapper(expr.Loc, e.Index)
|
|
didLower = true
|
|
break
|
|
}
|
|
|
|
// "[a.#b] = [c]" => "[__privateWrapper(a, #b)._] = [c]"
|
|
if private, ok := e.Index.Data.(*js_ast.EPrivateIdentifier); ok && p.privateSymbolNeedsToBeLowered(private) {
|
|
var target js_ast.Expr
|
|
|
|
switch p.symbols[private.Ref.InnerIndex].Kind {
|
|
case ast.SymbolPrivateSet, ast.SymbolPrivateStaticSet,
|
|
ast.SymbolPrivateGetSetPair, ast.SymbolPrivateStaticGetSetPair:
|
|
// "this.#setter" => "__privateWrapper(this, #setter, setter_set)"
|
|
fnRef := p.privateSetters[private.Ref]
|
|
p.recordUsage(fnRef)
|
|
target = p.callRuntime(expr.Loc, "__privateWrapper", []js_ast.Expr{
|
|
e.Target,
|
|
{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: fnRef}},
|
|
})
|
|
|
|
default:
|
|
// "this.#field" => "__privateWrapper(this, #field)"
|
|
target = p.callRuntime(expr.Loc, "__privateWrapper", []js_ast.Expr{
|
|
e.Target,
|
|
{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
|
|
})
|
|
}
|
|
|
|
// "__privateWrapper(this, #field)" => "__privateWrapper(this, #field)._"
|
|
expr.Data = &js_ast.EDot{Target: target, Name: "_", NameLoc: expr.Loc}
|
|
didLower = true
|
|
}
|
|
|
|
case *js_ast.EArray:
|
|
for i, item := range e.Items {
|
|
if item, ok := p.lowerSuperPropertyOrPrivateInAssign(item); ok {
|
|
e.Items[i] = item
|
|
didLower = true
|
|
}
|
|
}
|
|
|
|
case *js_ast.EObject:
|
|
for i, property := range e.Properties {
|
|
if property.ValueOrNil.Data != nil {
|
|
if value, ok := p.lowerSuperPropertyOrPrivateInAssign(property.ValueOrNil); ok {
|
|
e.Properties[i].ValueOrNil = value
|
|
didLower = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return expr, didLower
|
|
}
|
|
|
|
func (p *parser) shouldLowerSuperPropertyAccess(expr js_ast.Expr) bool {
|
|
if p.fnOrArrowDataVisit.shouldLowerSuperPropertyAccess {
|
|
_, isSuper := expr.Data.(*js_ast.ESuper)
|
|
return isSuper
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *parser) callSuperPropertyWrapper(loc logger.Loc, key js_ast.Expr) js_ast.Expr {
|
|
ref := *p.fnOnlyDataVisit.innerClassNameRef
|
|
p.recordUsage(ref)
|
|
class := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
this := js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
|
|
// Handle "this" in lowered static class field initializers
|
|
if p.fnOnlyDataVisit.shouldReplaceThisWithInnerClassNameRef {
|
|
p.recordUsage(ref)
|
|
this.Data = &js_ast.EIdentifier{Ref: ref}
|
|
}
|
|
|
|
if !p.fnOnlyDataVisit.isInStaticClassContext {
|
|
// "super.foo" => "__superWrapper(Class.prototype, this, 'foo')._"
|
|
// "super[foo]" => "__superWrapper(Class.prototype, this, foo)._"
|
|
class.Data = &js_ast.EDot{Target: class, NameLoc: loc, Name: "prototype"}
|
|
}
|
|
|
|
return js_ast.Expr{Loc: loc, Data: &js_ast.EDot{Target: p.callRuntime(loc, "__superWrapper", []js_ast.Expr{
|
|
class,
|
|
this,
|
|
key,
|
|
}), Name: "_", NameLoc: loc}}
|
|
}
|
|
|
|
func (p *parser) lowerSuperPropertyGet(loc logger.Loc, key js_ast.Expr) js_ast.Expr {
|
|
ref := *p.fnOnlyDataVisit.innerClassNameRef
|
|
p.recordUsage(ref)
|
|
class := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
this := js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
|
|
// Handle "this" in lowered static class field initializers
|
|
if p.fnOnlyDataVisit.shouldReplaceThisWithInnerClassNameRef {
|
|
p.recordUsage(ref)
|
|
this.Data = &js_ast.EIdentifier{Ref: ref}
|
|
}
|
|
|
|
if !p.fnOnlyDataVisit.isInStaticClassContext {
|
|
// "super.foo" => "__superGet(Class.prototype, this, 'foo')"
|
|
// "super[foo]" => "__superGet(Class.prototype, this, foo)"
|
|
class.Data = &js_ast.EDot{Target: class, NameLoc: loc, Name: "prototype"}
|
|
}
|
|
|
|
return p.callRuntime(loc, "__superGet", []js_ast.Expr{
|
|
class,
|
|
this,
|
|
key,
|
|
})
|
|
}
|
|
|
|
func (p *parser) lowerSuperPropertySet(loc logger.Loc, key js_ast.Expr, value js_ast.Expr) js_ast.Expr {
|
|
// "super.foo = bar" => "__superSet(Class, this, 'foo', bar)"
|
|
// "super[foo] = bar" => "__superSet(Class, this, foo, bar)"
|
|
ref := *p.fnOnlyDataVisit.innerClassNameRef
|
|
p.recordUsage(ref)
|
|
class := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
this := js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
|
|
// Handle "this" in lowered static class field initializers
|
|
if p.fnOnlyDataVisit.shouldReplaceThisWithInnerClassNameRef {
|
|
p.recordUsage(ref)
|
|
this.Data = &js_ast.EIdentifier{Ref: ref}
|
|
}
|
|
|
|
if !p.fnOnlyDataVisit.isInStaticClassContext {
|
|
// "super.foo = bar" => "__superSet(Class.prototype, this, 'foo', bar)"
|
|
// "super[foo] = bar" => "__superSet(Class.prototype, this, foo, bar)"
|
|
class.Data = &js_ast.EDot{Target: class, NameLoc: loc, Name: "prototype"}
|
|
}
|
|
|
|
return p.callRuntime(loc, "__superSet", []js_ast.Expr{
|
|
class,
|
|
this,
|
|
key,
|
|
value,
|
|
})
|
|
}
|
|
|
|
func (p *parser) lowerSuperPropertySetBinOp(loc logger.Loc, property js_ast.Expr, op js_ast.OpCode, value js_ast.Expr) js_ast.Expr {
|
|
// "super.foo += bar" => "__superSet(Class, this, 'foo', __superGet(Class, this, 'foo') + bar)"
|
|
// "super[foo] += bar" => "__superSet(Class, this, foo, __superGet(Class, this, foo) + bar)"
|
|
// "super[foo()] += bar" => "__superSet(Class, this, _a = foo(), __superGet(Class, this, _a) + bar)"
|
|
targetFunc, targetWrapFunc := p.captureValueWithPossibleSideEffects(property.Loc, 2, property, valueDefinitelyNotMutated)
|
|
return targetWrapFunc(p.lowerSuperPropertySet(loc, targetFunc(), js_ast.Expr{Loc: value.Loc, Data: &js_ast.EBinary{
|
|
Op: op,
|
|
Left: p.lowerSuperPropertyGet(loc, targetFunc()),
|
|
Right: value,
|
|
}}))
|
|
}
|
|
|
|
func (p *parser) maybeLowerSuperPropertyGetInsideCall(call *js_ast.ECall) {
|
|
var key js_ast.Expr
|
|
|
|
switch e := call.Target.Data.(type) {
|
|
case *js_ast.EDot:
|
|
// Lower "super.prop" if necessary
|
|
if !p.shouldLowerSuperPropertyAccess(e.Target) {
|
|
return
|
|
}
|
|
key = js_ast.Expr{Loc: e.NameLoc, Data: &js_ast.EString{Value: helpers.StringToUTF16(e.Name)}}
|
|
|
|
case *js_ast.EIndex:
|
|
// Lower "super[prop]" if necessary
|
|
if !p.shouldLowerSuperPropertyAccess(e.Target) {
|
|
return
|
|
}
|
|
key = e.Index
|
|
|
|
default:
|
|
return
|
|
}
|
|
|
|
// "super.foo(a, b)" => "__superGet(Class, this, 'foo').call(this, a, b)"
|
|
call.Target.Data = &js_ast.EDot{
|
|
Target: p.lowerSuperPropertyGet(call.Target.Loc, key),
|
|
NameLoc: key.Loc,
|
|
Name: "call",
|
|
}
|
|
thisExpr := js_ast.Expr{Loc: call.Target.Loc, Data: js_ast.EThisShared}
|
|
call.Args = append([]js_ast.Expr{thisExpr}, call.Args...)
|
|
}
|
|
|
|
type classLoweringInfo struct {
|
|
lowerAllInstanceFields bool
|
|
lowerAllStaticFields bool
|
|
shimSuperCtorCalls bool
|
|
}
|
|
|
|
func (p *parser) computeClassLoweringInfo(class *js_ast.Class) (result classLoweringInfo) {
|
|
// Name keeping for classes is implemented with a static block. So we need to
|
|
// lower all static fields if static blocks are unsupported so that the name
|
|
// keeping comes first before other static initializers.
|
|
if p.options.keepNames && p.options.unsupportedJSFeatures.Has(compat.ClassStaticBlocks) {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
|
|
// TypeScript's "experimentalDecorators" feature replaces all references of
|
|
// the class name with the decorated class after class decorators have run.
|
|
// This cannot be done by only reassigning to the class symbol in JavaScript
|
|
// because it's shadowed by the class name within the class body. Instead,
|
|
// we need to hoist all code in static contexts out of the class body so
|
|
// that it's no longer shadowed:
|
|
//
|
|
// const decorate = x => ({ x })
|
|
// @decorate
|
|
// class Foo {
|
|
// static oldFoo = Foo
|
|
// static newFoo = () => Foo
|
|
// }
|
|
// console.log('This must be false:', Foo.x.oldFoo === Foo.x.newFoo())
|
|
//
|
|
if p.options.ts.Parse && p.options.ts.Config.ExperimentalDecorators == config.True && len(class.Decorators) > 0 {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
|
|
// If something has decorators, just lower everything for now. It's possible
|
|
// that we could avoid lowering in certain cases, but doing so is very tricky
|
|
// due to the complexity of the decorator specification. The specification is
|
|
// also still evolving so trying to optimize it now is also potentially
|
|
// premature.
|
|
if class.ShouldLowerStandardDecorators {
|
|
for _, prop := range class.Properties {
|
|
if len(prop.Decorators) > 0 {
|
|
for _, prop := range class.Properties {
|
|
if private, ok := prop.Key.Data.(*js_ast.EPrivateIdentifier); ok {
|
|
p.symbols[private.Ref.InnerIndex].Flags |= ast.PrivateSymbolMustBeLowered
|
|
}
|
|
}
|
|
result.lowerAllStaticFields = true
|
|
result.lowerAllInstanceFields = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Conservatively lower fields of a given type (instance or static) when any
|
|
// member of that type needs to be lowered. This must be done to preserve
|
|
// evaluation order. For example:
|
|
//
|
|
// class Foo {
|
|
// #foo = 123
|
|
// bar = this.#foo
|
|
// }
|
|
//
|
|
// It would be bad if we transformed that into something like this:
|
|
//
|
|
// var _foo;
|
|
// class Foo {
|
|
// constructor() {
|
|
// _foo.set(this, 123);
|
|
// }
|
|
// bar = __privateGet(this, _foo);
|
|
// }
|
|
// _foo = new WeakMap();
|
|
//
|
|
// That evaluates "bar" then "foo" instead of "foo" then "bar" like the
|
|
// original code. We need to do this instead:
|
|
//
|
|
// var _foo;
|
|
// class Foo {
|
|
// constructor() {
|
|
// _foo.set(this, 123);
|
|
// __publicField(this, "bar", __privateGet(this, _foo));
|
|
// }
|
|
// }
|
|
// _foo = new WeakMap();
|
|
//
|
|
for _, prop := range class.Properties {
|
|
if prop.Kind == js_ast.PropertyClassStaticBlock {
|
|
if p.options.unsupportedJSFeatures.Has(compat.ClassStaticBlocks) {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
if private, ok := prop.Key.Data.(*js_ast.EPrivateIdentifier); ok {
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
if p.privateSymbolNeedsToBeLowered(private) {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
} else {
|
|
if p.privateSymbolNeedsToBeLowered(private) {
|
|
result.lowerAllInstanceFields = true
|
|
|
|
// We can't transform this:
|
|
//
|
|
// class Foo {
|
|
// #foo = 123
|
|
// static bar = new Foo().#foo
|
|
// }
|
|
//
|
|
// into this:
|
|
//
|
|
// var _foo;
|
|
// const _Foo = class {
|
|
// constructor() {
|
|
// _foo.set(this, 123);
|
|
// }
|
|
// static bar = __privateGet(new _Foo(), _foo);
|
|
// };
|
|
// let Foo = _Foo;
|
|
// _foo = new WeakMap();
|
|
//
|
|
// because "_Foo" won't be initialized in the initializer for "bar".
|
|
// So we currently lower all static fields in this case too. This
|
|
// isn't great and it would be good to find a way to avoid this.
|
|
// The inner class name symbol substitution mechanism should probably
|
|
// be rethought.
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
if prop.Kind == js_ast.PropertyAutoAccessor {
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
if p.options.unsupportedJSFeatures.Has(compat.ClassPrivateStaticField) {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
} else {
|
|
if p.options.unsupportedJSFeatures.Has(compat.ClassPrivateField) {
|
|
result.lowerAllInstanceFields = true
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// This doesn't come before the private member check above because
|
|
// unsupported private methods must also trigger field lowering:
|
|
//
|
|
// class Foo {
|
|
// bar = this.#foo()
|
|
// #foo() {}
|
|
// }
|
|
//
|
|
// It would be bad if we transformed that to something like this:
|
|
//
|
|
// var _foo, foo_fn;
|
|
// class Foo {
|
|
// constructor() {
|
|
// _foo.add(this);
|
|
// }
|
|
// bar = __privateMethod(this, _foo, foo_fn).call(this);
|
|
// }
|
|
// _foo = new WeakSet();
|
|
// foo_fn = function() {
|
|
// };
|
|
//
|
|
// 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.Kind.IsMethodDefinition() {
|
|
// 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
|
|
}
|
|
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
// Static fields must be lowered if the target doesn't support them
|
|
if p.options.unsupportedJSFeatures.Has(compat.ClassStaticField) {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
|
|
// Convert static fields to assignment statements if the TypeScript
|
|
// setting for this is enabled. I don't think this matters for private
|
|
// fields because there's no way for this to call a setter in the base
|
|
// class, so this isn't done for private fields.
|
|
//
|
|
// If class static blocks are supported, then we can do this inline
|
|
// without needing to move the initializers outside of the class body.
|
|
// Otherwise, we need to lower all static class fields.
|
|
if p.options.ts.Parse && !class.UseDefineForClassFields && p.options.unsupportedJSFeatures.Has(compat.ClassStaticBlocks) {
|
|
result.lowerAllStaticFields = true
|
|
}
|
|
} else {
|
|
if p.options.ts.Parse && !class.UseDefineForClassFields {
|
|
// Convert instance fields to assignment statements if the TypeScript
|
|
// setting for this is enabled. I don't think this matters for private
|
|
// fields because there's no way for this to call a setter in the base
|
|
// class, so this isn't done for private fields.
|
|
if prop.InitializerOrNil.Data != nil {
|
|
// We can skip lowering all instance fields if all instance fields
|
|
// disappear completely when lowered. This happens when
|
|
// "useDefineForClassFields" is false and there is no initializer.
|
|
result.lowerAllInstanceFields = true
|
|
}
|
|
} else if p.options.unsupportedJSFeatures.Has(compat.ClassField) {
|
|
// Instance fields must be lowered if the target doesn't support them
|
|
result.lowerAllInstanceFields = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
type classKind uint8
|
|
|
|
const (
|
|
classKindExpr classKind = iota
|
|
classKindStmt
|
|
classKindExportStmt
|
|
classKindExportDefaultStmt
|
|
)
|
|
|
|
type lowerClassContext struct {
|
|
nameToKeep string
|
|
kind classKind
|
|
class *js_ast.Class
|
|
classLoc logger.Loc
|
|
classExpr js_ast.Expr // Only for "kind == classKindExpr", may be replaced by "nameFunc()"
|
|
defaultName ast.LocRef
|
|
|
|
ctor *js_ast.EFunction
|
|
extendsRef ast.Ref
|
|
parameterFieldProps []js_ast.Property
|
|
parameterFields []js_ast.Stmt
|
|
instanceMembers []js_ast.Stmt
|
|
instancePrivateMethods []js_ast.Stmt
|
|
autoAccessorCount int
|
|
|
|
// These expressions are generated after the class body, in this order
|
|
computedPropertyChain js_ast.Expr
|
|
privateMembers []js_ast.Expr
|
|
staticMembers []js_ast.Expr
|
|
staticPrivateMethods []js_ast.Expr
|
|
|
|
// These contain calls to "__decorateClass" for TypeScript experimental decorators
|
|
instanceExperimentalDecorators []js_ast.Expr
|
|
staticExperimentalDecorators []js_ast.Expr
|
|
|
|
// These are used for implementing JavaScript decorators
|
|
decoratorContextRef ast.Ref
|
|
decoratorClassDecorators js_ast.Expr
|
|
decoratorPropertyToInitializerMap map[int]int
|
|
decoratorCallInstanceMethodExtraInitializers bool
|
|
decoratorCallStaticMethodExtraInitializers bool
|
|
decoratorStaticNonFieldElements []js_ast.Expr
|
|
decoratorInstanceNonFieldElements []js_ast.Expr
|
|
decoratorStaticFieldElements []js_ast.Expr
|
|
decoratorInstanceFieldElements []js_ast.Expr
|
|
|
|
// These are used by "lowerMethod"
|
|
privateInstanceMethodRef ast.Ref
|
|
privateStaticMethodRef ast.Ref
|
|
|
|
// These are only for class expressions that need to be captured
|
|
nameFunc func() js_ast.Expr
|
|
wrapFunc func(js_ast.Expr) js_ast.Expr
|
|
didCaptureClassExpr bool
|
|
}
|
|
|
|
// Apply all relevant transforms to a class object (either a statement or an
|
|
// expression) including:
|
|
//
|
|
// - Transforming class fields for older environments
|
|
// - Transforming static blocks for older environments
|
|
// - Transforming TypeScript experimental decorators into JavaScript
|
|
// - Transforming TypeScript class fields into assignments for "useDefineForClassFields"
|
|
//
|
|
// Note that this doesn't transform any nested AST subtrees inside the class
|
|
// body (e.g. the contents of initializers, methods, and static blocks). Those
|
|
// have already been transformed by "visitClass" by this point. It's done that
|
|
// way for performance so that we don't need to do another AST pass.
|
|
func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, result visitClassResult, nameToKeep string) ([]js_ast.Stmt, js_ast.Expr) {
|
|
ctx := lowerClassContext{
|
|
nameToKeep: nameToKeep,
|
|
extendsRef: ast.InvalidRef,
|
|
decoratorContextRef: ast.InvalidRef,
|
|
privateInstanceMethodRef: ast.InvalidRef,
|
|
privateStaticMethodRef: ast.InvalidRef,
|
|
}
|
|
|
|
// Unpack the class from the statement or expression
|
|
if stmt.Data == nil {
|
|
e, _ := expr.Data.(*js_ast.EClass)
|
|
ctx.class = &e.Class
|
|
ctx.classExpr = expr
|
|
ctx.kind = classKindExpr
|
|
if ctx.class.Name != nil {
|
|
symbol := &p.symbols[ctx.class.Name.Ref.InnerIndex]
|
|
ctx.nameToKeep = symbol.OriginalName
|
|
|
|
// The inner class name inside the class expression should be the same as
|
|
// the class expression name itself
|
|
if result.innerClassNameRef != ast.InvalidRef {
|
|
p.mergeSymbols(result.innerClassNameRef, ctx.class.Name.Ref)
|
|
}
|
|
|
|
// Remove unused class names when minifying. Check this after we merge in
|
|
// the inner class name above since that will adjust the use count.
|
|
if p.options.minifySyntax && symbol.UseCountEstimate == 0 {
|
|
ctx.class.Name = nil
|
|
}
|
|
}
|
|
} else if s, ok := stmt.Data.(*js_ast.SClass); ok {
|
|
ctx.class = &s.Class
|
|
if ctx.class.Name != nil {
|
|
ctx.nameToKeep = p.symbols[ctx.class.Name.Ref.InnerIndex].OriginalName
|
|
}
|
|
if s.IsExport {
|
|
ctx.kind = classKindExportStmt
|
|
} else {
|
|
ctx.kind = classKindStmt
|
|
}
|
|
} else {
|
|
s, _ := stmt.Data.(*js_ast.SExportDefault)
|
|
s2, _ := s.Value.Data.(*js_ast.SClass)
|
|
ctx.class = &s2.Class
|
|
if ctx.class.Name != nil {
|
|
ctx.nameToKeep = p.symbols[ctx.class.Name.Ref.InnerIndex].OriginalName
|
|
}
|
|
ctx.defaultName = s.DefaultName
|
|
ctx.kind = classKindExportDefaultStmt
|
|
}
|
|
if stmt.Data == nil {
|
|
ctx.classLoc = expr.Loc
|
|
} else {
|
|
ctx.classLoc = stmt.Loc
|
|
}
|
|
|
|
classLoweringInfo := p.computeClassLoweringInfo(ctx.class)
|
|
ctx.enableNameCapture(p, result)
|
|
ctx.processProperties(p, classLoweringInfo, result)
|
|
ctx.insertInitializersIntoConstructor(p, classLoweringInfo, result)
|
|
return ctx.finishAndGenerateCode(p, result)
|
|
}
|
|
|
|
func (ctx *lowerClassContext) enableNameCapture(p *parser, result visitClassResult) {
|
|
// Class statements can be missing a name if they are in an
|
|
// "export default" statement:
|
|
//
|
|
// export default class {
|
|
// static foo = 123
|
|
// }
|
|
//
|
|
ctx.nameFunc = func() js_ast.Expr {
|
|
if ctx.kind == classKindExpr {
|
|
// If this is a class expression, capture and store it. We have to
|
|
// do this even if it has a name since the name isn't exposed
|
|
// outside the class body.
|
|
classExpr := &js_ast.EClass{Class: *ctx.class}
|
|
ctx.class = &classExpr.Class
|
|
ctx.nameFunc, ctx.wrapFunc = p.captureValueWithPossibleSideEffects(ctx.classLoc, 2, js_ast.Expr{Loc: ctx.classLoc, Data: classExpr}, valueDefinitelyNotMutated)
|
|
ctx.classExpr = ctx.nameFunc()
|
|
ctx.didCaptureClassExpr = true
|
|
name := ctx.nameFunc()
|
|
|
|
// If we're storing the class expression in a variable, remove the class
|
|
// name and rewrite all references to the class name with references to
|
|
// the temporary variable holding the class expression. This ensures that
|
|
// references to the class expression by name in any expressions that end
|
|
// up being pulled outside of the class body still work. For example:
|
|
//
|
|
// let Bar = class Foo {
|
|
// static foo = 123
|
|
// static bar = Foo.foo
|
|
// }
|
|
//
|
|
// This might be converted into the following:
|
|
//
|
|
// var _a;
|
|
// let Bar = (_a = class {
|
|
// }, _a.foo = 123, _a.bar = _a.foo, _a);
|
|
//
|
|
if ctx.class.Name != nil {
|
|
p.mergeSymbols(ctx.class.Name.Ref, name.Data.(*js_ast.EIdentifier).Ref)
|
|
ctx.class.Name = nil
|
|
}
|
|
|
|
return name
|
|
} else {
|
|
// If anything referenced the inner class name, then we should use that
|
|
// name for any automatically-generated initialization code, since it
|
|
// will come before the outer class name is initialized.
|
|
if result.innerClassNameRef != ast.InvalidRef {
|
|
p.recordUsage(result.innerClassNameRef)
|
|
return js_ast.Expr{Loc: ctx.class.Name.Loc, Data: &js_ast.EIdentifier{Ref: result.innerClassNameRef}}
|
|
}
|
|
|
|
// Otherwise we should just use the outer class name
|
|
if ctx.class.Name == nil {
|
|
if ctx.kind == classKindExportDefaultStmt {
|
|
ctx.class.Name = &ctx.defaultName
|
|
} else {
|
|
ctx.class.Name = &ast.LocRef{Loc: ctx.classLoc, Ref: p.generateTempRef(tempRefNoDeclare, "")}
|
|
}
|
|
}
|
|
p.recordUsage(ctx.class.Name.Ref)
|
|
return js_ast.Expr{Loc: ctx.class.Name.Loc, Data: &js_ast.EIdentifier{Ref: ctx.class.Name.Ref}}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle lowering of instance and static fields. Move their initializers
|
|
// from the class body to either the constructor (instance fields) or after
|
|
// the class (static fields).
|
|
//
|
|
// If this returns true, the return property should be added to the class
|
|
// body. Otherwise the property should be omitted from the class body.
|
|
func (ctx *lowerClassContext) lowerField(
|
|
p *parser,
|
|
prop js_ast.Property,
|
|
private *js_ast.EPrivateIdentifier,
|
|
shouldOmitFieldInitializer bool,
|
|
staticFieldToBlockAssign bool,
|
|
initializerIndex int,
|
|
) (js_ast.Property, ast.Ref, bool) {
|
|
mustLowerPrivate := private != nil && p.privateSymbolNeedsToBeLowered(private)
|
|
ref := ast.InvalidRef
|
|
|
|
// The TypeScript compiler doesn't follow the JavaScript spec for
|
|
// uninitialized fields. They are supposed to be set to undefined but the
|
|
// TypeScript compiler just omits them entirely.
|
|
if !shouldOmitFieldInitializer {
|
|
loc := prop.Loc
|
|
|
|
// Determine where to store the field
|
|
var target js_ast.Expr
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) && !staticFieldToBlockAssign {
|
|
target = ctx.nameFunc()
|
|
} else {
|
|
target = js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
}
|
|
|
|
// Generate the assignment initializer
|
|
var init js_ast.Expr
|
|
if prop.InitializerOrNil.Data != nil {
|
|
init = prop.InitializerOrNil
|
|
} else {
|
|
init = js_ast.Expr{Loc: loc, Data: js_ast.EUndefinedShared}
|
|
}
|
|
|
|
// Optionally call registered decorator initializers
|
|
if initializerIndex != -1 {
|
|
var value js_ast.Expr
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
value = ctx.nameFunc()
|
|
} else {
|
|
value = js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
}
|
|
args := []js_ast.Expr{
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: loc, Data: &js_ast.ENumber{Value: float64((4 + 2*initializerIndex) << 1)}},
|
|
value,
|
|
}
|
|
if _, ok := init.Data.(*js_ast.EUndefined); !ok {
|
|
args = append(args, init)
|
|
}
|
|
init = p.callRuntime(init.Loc, "__runInitializers", args)
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
}
|
|
|
|
// Generate the assignment target
|
|
var memberExpr js_ast.Expr
|
|
if mustLowerPrivate {
|
|
// Generate a new symbol for this private field
|
|
ref = p.generateTempRef(tempRefNeedsDeclare, "_"+p.symbols[private.Ref.InnerIndex].OriginalName[1:])
|
|
p.symbols[private.Ref.InnerIndex].Link = ref
|
|
|
|
// Initialize the private field to a new WeakMap
|
|
if p.weakMapRef == ast.InvalidRef {
|
|
p.weakMapRef = p.newSymbol(ast.SymbolUnbound, "WeakMap")
|
|
p.moduleScope.Generated = append(p.moduleScope.Generated, p.weakMapRef)
|
|
}
|
|
ctx.privateMembers = append(ctx.privateMembers, js_ast.Assign(
|
|
js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}},
|
|
js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.ENew{Target: js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: p.weakMapRef}}}},
|
|
))
|
|
p.recordUsage(ref)
|
|
|
|
// Add every newly-constructed instance into this map
|
|
key := js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
args := []js_ast.Expr{target, key}
|
|
if _, ok := init.Data.(*js_ast.EUndefined); !ok {
|
|
args = append(args, init)
|
|
}
|
|
memberExpr = p.callRuntime(loc, "__privateAdd", args)
|
|
p.recordUsage(ref)
|
|
} else if private == nil && ctx.class.UseDefineForClassFields {
|
|
if p.shouldAddKeyComment {
|
|
if str, ok := prop.Key.Data.(*js_ast.EString); ok {
|
|
str.HasPropertyKeyComment = true
|
|
}
|
|
}
|
|
args := []js_ast.Expr{target, prop.Key}
|
|
if _, ok := init.Data.(*js_ast.EUndefined); !ok {
|
|
args = append(args, init)
|
|
}
|
|
memberExpr = js_ast.Expr{Loc: loc, Data: &js_ast.ECall{
|
|
Target: p.importFromRuntime(loc, "__publicField"),
|
|
Args: args,
|
|
}}
|
|
} else {
|
|
if key, ok := prop.Key.Data.(*js_ast.EString); ok && !prop.Flags.Has(js_ast.PropertyIsComputed) && !prop.Flags.Has(js_ast.PropertyPreferQuotedKey) {
|
|
target = js_ast.Expr{Loc: loc, Data: &js_ast.EDot{
|
|
Target: target,
|
|
Name: helpers.UTF16ToString(key.Value),
|
|
NameLoc: prop.Key.Loc,
|
|
}}
|
|
} else {
|
|
target = js_ast.Expr{Loc: loc, Data: &js_ast.EIndex{
|
|
Target: target,
|
|
Index: prop.Key,
|
|
}}
|
|
}
|
|
|
|
memberExpr = js_ast.Assign(target, init)
|
|
}
|
|
|
|
// Run extra initializers
|
|
if initializerIndex != -1 {
|
|
var value js_ast.Expr
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
value = ctx.nameFunc()
|
|
} else {
|
|
value = js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
}
|
|
memberExpr = js_ast.JoinWithComma(memberExpr, p.callRuntime(loc, "__runInitializers", []js_ast.Expr{
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: loc, Data: &js_ast.ENumber{Value: float64(((5 + 2*initializerIndex) << 1) | 1)}},
|
|
value,
|
|
}))
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
}
|
|
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
// Move this property to an assignment after the class ends
|
|
if staticFieldToBlockAssign {
|
|
// Use inline assignment in a static block instead of lowering
|
|
return js_ast.Property{
|
|
Loc: loc,
|
|
Kind: js_ast.PropertyClassStaticBlock,
|
|
ClassStaticBlock: &js_ast.ClassStaticBlock{
|
|
Loc: loc,
|
|
Block: js_ast.SBlock{Stmts: []js_ast.Stmt{
|
|
{Loc: loc, Data: &js_ast.SExpr{Value: memberExpr}}},
|
|
},
|
|
},
|
|
}, ref, true
|
|
} else {
|
|
// Move this property to an assignment after the class ends
|
|
ctx.staticMembers = append(ctx.staticMembers, memberExpr)
|
|
}
|
|
} else {
|
|
// Move this property to an assignment inside the class constructor
|
|
ctx.instanceMembers = append(ctx.instanceMembers, js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{Value: memberExpr}})
|
|
}
|
|
}
|
|
|
|
if private == nil || mustLowerPrivate {
|
|
// Remove the field from the class body
|
|
return js_ast.Property{}, ref, false
|
|
}
|
|
|
|
// Keep the private field but remove the initializer
|
|
prop.InitializerOrNil = js_ast.Expr{}
|
|
return prop, ref, true
|
|
}
|
|
|
|
func (ctx *lowerClassContext) lowerPrivateMethod(p *parser, prop js_ast.Property, private *js_ast.EPrivateIdentifier) {
|
|
// All private methods can share the same WeakSet
|
|
var ref *ast.Ref
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
ref = &ctx.privateStaticMethodRef
|
|
} else {
|
|
ref = &ctx.privateInstanceMethodRef
|
|
}
|
|
if *ref == ast.InvalidRef {
|
|
// Generate a new symbol to store the WeakSet
|
|
var name string
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
name = "_static"
|
|
} else {
|
|
name = "_instances"
|
|
}
|
|
if ctx.nameToKeep != "" {
|
|
name = fmt.Sprintf("_%s%s", ctx.nameToKeep, name)
|
|
}
|
|
*ref = p.generateTempRef(tempRefNeedsDeclare, name)
|
|
|
|
// Generate the initializer
|
|
if p.weakSetRef == ast.InvalidRef {
|
|
p.weakSetRef = p.newSymbol(ast.SymbolUnbound, "WeakSet")
|
|
p.moduleScope.Generated = append(p.moduleScope.Generated, p.weakSetRef)
|
|
}
|
|
ctx.privateMembers = append(ctx.privateMembers, js_ast.Assign(
|
|
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: *ref}},
|
|
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.ENew{Target: js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: p.weakSetRef}}}},
|
|
))
|
|
p.recordUsage(*ref)
|
|
p.recordUsage(p.weakSetRef)
|
|
|
|
// Determine what to store in the WeakSet
|
|
var target js_ast.Expr
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
target = ctx.nameFunc()
|
|
} else {
|
|
target = js_ast.Expr{Loc: ctx.classLoc, Data: js_ast.EThisShared}
|
|
}
|
|
|
|
// Add every newly-constructed instance into this set
|
|
methodExpr := p.callRuntime(ctx.classLoc, "__privateAdd", []js_ast.Expr{
|
|
target,
|
|
{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: *ref}},
|
|
})
|
|
p.recordUsage(*ref)
|
|
|
|
// Make sure that adding to the map happens before any field
|
|
// initializers to handle cases like this:
|
|
//
|
|
// class A {
|
|
// pub = this.#priv;
|
|
// #priv() {}
|
|
// }
|
|
//
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
// Move this property to an assignment after the class ends
|
|
ctx.staticPrivateMethods = append(ctx.staticPrivateMethods, methodExpr)
|
|
} else {
|
|
// Move this property to an assignment inside the class constructor
|
|
ctx.instancePrivateMethods = append(ctx.instancePrivateMethods, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SExpr{Value: methodExpr}})
|
|
}
|
|
}
|
|
p.symbols[private.Ref.InnerIndex].Link = *ref
|
|
}
|
|
|
|
// If this returns true, the method property should be dropped as it has
|
|
// already been accounted for elsewhere (e.g. a lowered private method).
|
|
func (ctx *lowerClassContext) lowerMethod(p *parser, prop js_ast.Property, private *js_ast.EPrivateIdentifier) bool {
|
|
if private != nil && p.privateSymbolNeedsToBeLowered(private) {
|
|
ctx.lowerPrivateMethod(p, prop, private)
|
|
|
|
// Move the method definition outside the class body
|
|
methodRef := p.generateTempRef(tempRefNeedsDeclare, "_")
|
|
if prop.Kind == js_ast.PropertySetter {
|
|
p.symbols[methodRef.InnerIndex].Link = p.privateSetters[private.Ref]
|
|
} else {
|
|
p.symbols[methodRef.InnerIndex].Link = p.privateGetters[private.Ref]
|
|
}
|
|
p.recordUsage(methodRef)
|
|
ctx.privateMembers = append(ctx.privateMembers, js_ast.Assign(
|
|
js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: methodRef}},
|
|
prop.ValueOrNil,
|
|
))
|
|
return true
|
|
}
|
|
|
|
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 {
|
|
// Remember where the constructor is for later
|
|
ctx.ctor = fn
|
|
|
|
// Initialize TypeScript constructor parameter fields
|
|
if p.options.ts.Parse {
|
|
for _, arg := range ctx.ctor.Fn.Args {
|
|
if arg.IsTypeScriptCtorField {
|
|
if id, ok := arg.Binding.Data.(*js_ast.BIdentifier); ok {
|
|
loc := arg.Binding.Loc
|
|
name := p.symbols[id.Ref.InnerIndex].OriginalName
|
|
target := js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}
|
|
init := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: id.Ref}}
|
|
|
|
// See: https://github.com/evanw/esbuild/issues/4421
|
|
if !ctx.class.UseDefineForClassFields || !p.options.unsupportedJSFeatures.Has(compat.ClassField) {
|
|
ctx.parameterFields = append(ctx.parameterFields, js_ast.AssignStmt(
|
|
js_ast.Expr{Loc: loc, Data: p.dotOrMangledPropVisit(target, name, loc)},
|
|
init,
|
|
))
|
|
}
|
|
if ctx.class.UseDefineForClassFields {
|
|
key := js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(name)}}
|
|
if p.options.unsupportedJSFeatures.Has(compat.ClassField) {
|
|
ctx.parameterFields = append(ctx.parameterFields, js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{
|
|
Value: js_ast.Expr{Loc: loc, Data: &js_ast.ECall{
|
|
Target: p.importFromRuntime(loc, "__publicField"),
|
|
Args: []js_ast.Expr{target, key, init},
|
|
}},
|
|
}})
|
|
} else {
|
|
ctx.parameterFieldProps = append(ctx.parameterFieldProps, js_ast.Property{Kind: js_ast.PropertyField, Loc: loc, Key: key})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type propertyAnalysis struct {
|
|
private *js_ast.EPrivateIdentifier
|
|
propExperimentalDecorators []js_ast.Decorator
|
|
propDecorators []js_ast.Decorator
|
|
mustLowerField bool
|
|
needsValueOfKey bool
|
|
rewriteAutoAccessorToGetSet bool
|
|
shouldOmitFieldInitializer bool
|
|
staticFieldToBlockAssign bool
|
|
isComputedPropertyCopiedOrMoved bool
|
|
}
|
|
|
|
func (ctx *lowerClassContext) analyzeProperty(p *parser, prop js_ast.Property, classLoweringInfo classLoweringInfo) (analysis propertyAnalysis) {
|
|
// The TypeScript class field transform requires removing fields without
|
|
// initializers. If the field is removed, then we only need the key for
|
|
// its side effects and we don't need a temporary reference for the key.
|
|
// However, the TypeScript compiler doesn't remove the field when doing
|
|
// strict class field initialization, so we shouldn't either.
|
|
analysis.private, _ = prop.Key.Data.(*js_ast.EPrivateIdentifier)
|
|
mustLowerPrivate := analysis.private != nil && p.privateSymbolNeedsToBeLowered(analysis.private)
|
|
analysis.shouldOmitFieldInitializer = p.options.ts.Parse && !prop.Kind.IsMethodDefinition() && prop.InitializerOrNil.Data == nil &&
|
|
!ctx.class.UseDefineForClassFields && !mustLowerPrivate && !ctx.class.ShouldLowerStandardDecorators
|
|
|
|
// Class fields must be lowered if the environment doesn't support them
|
|
if !prop.Kind.IsMethodDefinition() {
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
analysis.mustLowerField = classLoweringInfo.lowerAllStaticFields
|
|
} else if prop.Kind == js_ast.PropertyField && p.options.ts.Parse && !ctx.class.UseDefineForClassFields && analysis.private == nil {
|
|
// Lower non-private instance fields (not accessors) if TypeScript's
|
|
// "useDefineForClassFields" setting is disabled. When all such fields
|
|
// have no initializers, we avoid setting the "lowerAllInstanceFields"
|
|
// flag as an optimization because we can just remove all class field
|
|
// declarations in that case without messing with the constructor. But
|
|
// we must set the "mustLowerField" flag here to cause this class field
|
|
// declaration to still be removed.
|
|
analysis.mustLowerField = true
|
|
} else {
|
|
analysis.mustLowerField = classLoweringInfo.lowerAllInstanceFields
|
|
}
|
|
}
|
|
|
|
// If the field uses the TypeScript "declare" or "abstract" keyword, just
|
|
// omit it entirely. However, we must still keep any side-effects in the
|
|
// computed value and/or in the decorators.
|
|
if prop.Kind == js_ast.PropertyDeclareOrAbstract && prop.ValueOrNil.Data == nil {
|
|
analysis.mustLowerField = true
|
|
analysis.shouldOmitFieldInitializer = true
|
|
}
|
|
|
|
// For convenience, split decorators off into separate fields based on how
|
|
// they will end up being lowered (if they are even being lowered at all)
|
|
if p.options.ts.Parse && p.options.ts.Config.ExperimentalDecorators == config.True {
|
|
analysis.propExperimentalDecorators = prop.Decorators
|
|
} else if ctx.class.ShouldLowerStandardDecorators {
|
|
analysis.propDecorators = prop.Decorators
|
|
}
|
|
|
|
// Note: Auto-accessors use a different transform when they are decorated.
|
|
// This transform trades off worse run-time performance for better code size.
|
|
analysis.rewriteAutoAccessorToGetSet = len(analysis.propDecorators) == 0 && prop.Kind == js_ast.PropertyAutoAccessor &&
|
|
(p.options.unsupportedJSFeatures.Has(compat.Decorators) || analysis.mustLowerField)
|
|
|
|
// Transform non-lowered static fields that use assign semantics into an
|
|
// assignment in an inline static block instead of lowering them. This lets
|
|
// us avoid having to unnecessarily lower static private fields when
|
|
// "useDefineForClassFields" is disabled.
|
|
analysis.staticFieldToBlockAssign = prop.Kind == js_ast.PropertyField && !analysis.mustLowerField && !ctx.class.UseDefineForClassFields &&
|
|
prop.Flags.Has(js_ast.PropertyIsStatic) && analysis.private == nil
|
|
|
|
// Computed properties can't be copied or moved because they have side effects
|
|
// and we don't want to evaluate their side effects twice or change their
|
|
// evaluation order. We'll need to store them in temporary variables to keep
|
|
// their side effects in place when we reference them elsewhere.
|
|
analysis.needsValueOfKey = true
|
|
if prop.Flags.Has(js_ast.PropertyIsComputed) &&
|
|
(len(analysis.propExperimentalDecorators) > 0 ||
|
|
len(analysis.propDecorators) > 0 ||
|
|
analysis.mustLowerField ||
|
|
analysis.staticFieldToBlockAssign ||
|
|
analysis.rewriteAutoAccessorToGetSet) {
|
|
analysis.isComputedPropertyCopiedOrMoved = true
|
|
|
|
// Determine if we don't actually need the value of the key (only the side
|
|
// effects). In that case we don't need a temporary variable.
|
|
if len(analysis.propExperimentalDecorators) == 0 &&
|
|
len(analysis.propDecorators) == 0 &&
|
|
!analysis.rewriteAutoAccessorToGetSet &&
|
|
analysis.shouldOmitFieldInitializer {
|
|
analysis.needsValueOfKey = false
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *parser) propertyNameHint(key js_ast.Expr) string {
|
|
switch k := key.Data.(type) {
|
|
case *js_ast.EString:
|
|
return helpers.UTF16ToString(k.Value)
|
|
case *js_ast.EIdentifier:
|
|
return p.symbols[k.Ref.InnerIndex].OriginalName
|
|
case *js_ast.EPrivateIdentifier:
|
|
return p.symbols[k.Ref.InnerIndex].OriginalName[1:]
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (ctx *lowerClassContext) hoistComputedProperties(p *parser, classLoweringInfo classLoweringInfo) (
|
|
propertyKeyTempRefs map[int]ast.Ref, decoratorTempRefs map[int]ast.Ref) {
|
|
var nextComputedPropertyKey *js_ast.Expr
|
|
|
|
// Computed property keys must be evaluated in a specific order for their
|
|
// side effects. This order must be preserved even when we have to move a
|
|
// class element around. For example, this can happen when using class fields
|
|
// with computed property keys and targeting environments without class field
|
|
// support. For example:
|
|
//
|
|
// class Foo {
|
|
// [a()]() {}
|
|
// static [b()] = null;
|
|
// [c()]() {}
|
|
// }
|
|
//
|
|
// If we need to lower the static field because static fields aren't supported,
|
|
// we still need to ensure that "b()" is called before "a()" and after "c()".
|
|
// That looks something like this:
|
|
//
|
|
// var _a;
|
|
// class Foo {
|
|
// [a()]() {}
|
|
// [(_a = b(), c())]() {}
|
|
// }
|
|
// __publicField(Foo, _a, null);
|
|
//
|
|
// Iterate in reverse so that any initializers are "pushed up" before the
|
|
// class body if there's nowhere else to put them. They can't be "pushed
|
|
// down" into a static block in the class body (the logical place to put
|
|
// them that's next in the evaluation order) because these expressions
|
|
// may contain "await" and static blocks do not allow "await".
|
|
for propIndex := len(ctx.class.Properties) - 1; propIndex >= 0; propIndex-- {
|
|
prop := &ctx.class.Properties[propIndex]
|
|
analysis := ctx.analyzeProperty(p, *prop, classLoweringInfo)
|
|
|
|
// Evaluate the decorator expressions inline before computed property keys
|
|
var decorators js_ast.Expr
|
|
if len(analysis.propDecorators) > 0 {
|
|
name := p.propertyNameHint(prop.Key)
|
|
if name != "" {
|
|
name = "_" + name
|
|
}
|
|
name += "_dec"
|
|
ref := p.generateTempRef(tempRefNeedsDeclare, name)
|
|
values := make([]js_ast.Expr, len(analysis.propDecorators))
|
|
for i, decorator := range analysis.propDecorators {
|
|
values[i] = decorator.Value
|
|
}
|
|
atLoc := analysis.propDecorators[0].AtLoc
|
|
decorators = js_ast.Assign(
|
|
js_ast.Expr{Loc: atLoc, Data: &js_ast.EIdentifier{Ref: ref}},
|
|
js_ast.Expr{Loc: atLoc, Data: &js_ast.EArray{Items: values, IsSingleLine: true}})
|
|
p.recordUsage(ref)
|
|
if decoratorTempRefs == nil {
|
|
decoratorTempRefs = make(map[int]ast.Ref)
|
|
}
|
|
decoratorTempRefs[propIndex] = ref
|
|
}
|
|
|
|
// Skip property keys that we know are side-effect free
|
|
switch prop.Key.Data.(type) {
|
|
case *js_ast.EString, *js_ast.ENameOfSymbol, *js_ast.ENumber, *js_ast.EPrivateIdentifier:
|
|
// Figure out where to stick the decorator side effects to preserve their order
|
|
if nextComputedPropertyKey != nil {
|
|
// Insert it before everything that comes after it
|
|
*nextComputedPropertyKey = js_ast.JoinWithComma(decorators, *nextComputedPropertyKey)
|
|
} else {
|
|
// Insert it after the first thing that comes before it
|
|
ctx.computedPropertyChain = js_ast.JoinWithComma(decorators, ctx.computedPropertyChain)
|
|
}
|
|
continue
|
|
|
|
default:
|
|
// Otherwise, evaluate the decorators right before the property key
|
|
if decorators.Data != nil {
|
|
prop.Key = js_ast.JoinWithComma(decorators, prop.Key)
|
|
prop.Flags |= js_ast.PropertyIsComputed
|
|
}
|
|
}
|
|
|
|
// If this key is referenced elsewhere, make sure to still preserve
|
|
// its side effects in the property's original location
|
|
if analysis.isComputedPropertyCopiedOrMoved {
|
|
// If this property is being duplicated instead of moved or removed, then
|
|
// we still need the assignment to the temporary so that we can reference
|
|
// it in multiple places, but we don't have to hoist the assignment to an
|
|
// earlier property (since this property is still there). In that case
|
|
// we can reduce generated code size by avoiding the hoist. One example
|
|
// of this case is a decorator on a class element with a computed
|
|
// property key:
|
|
//
|
|
// class Foo {
|
|
// @dec [a()]() {}
|
|
// }
|
|
//
|
|
// We want to do this:
|
|
//
|
|
// var _a;
|
|
// class Foo {
|
|
// [_a = a()]() {}
|
|
// }
|
|
// __decorateClass([dec], Foo.prototype, _a, 1);
|
|
//
|
|
// instead of this:
|
|
//
|
|
// var _a;
|
|
// _a = a();
|
|
// class Foo {
|
|
// [_a]() {}
|
|
// }
|
|
// __decorateClass([dec], Foo.prototype, _a, 1);
|
|
//
|
|
// So only do the hoist if this property is being moved or removed.
|
|
if !analysis.rewriteAutoAccessorToGetSet && (analysis.mustLowerField || analysis.staticFieldToBlockAssign) {
|
|
inlineKey := prop.Key
|
|
|
|
if !analysis.needsValueOfKey {
|
|
// In certain cases, we only need to evaluate a property key for its
|
|
// side effects but we don't actually need the value of the key itself.
|
|
// For example, a TypeScript class field without an initializer is
|
|
// omitted when TypeScript's "useDefineForClassFields" setting is false.
|
|
} else {
|
|
// Store the key in a temporary so we can refer to it later
|
|
ref := p.generateTempRef(tempRefNeedsDeclare, "")
|
|
inlineKey = js_ast.Assign(js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}}, prop.Key)
|
|
p.recordUsage(ref)
|
|
|
|
// Replace this property key with a reference to the temporary. We
|
|
// don't need to store the temporary in the "propertyKeyTempRefs"
|
|
// map because all references will refer to the temporary, not just
|
|
// some of them.
|
|
prop.Key = js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}}
|
|
p.recordUsage(ref)
|
|
}
|
|
|
|
// Figure out where to stick this property's side effect to preserve its order
|
|
if nextComputedPropertyKey != nil {
|
|
// Insert it before everything that comes after it
|
|
*nextComputedPropertyKey = js_ast.JoinWithComma(inlineKey, *nextComputedPropertyKey)
|
|
} else {
|
|
// Insert it after the first thing that comes before it
|
|
ctx.computedPropertyChain = js_ast.JoinWithComma(inlineKey, ctx.computedPropertyChain)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Otherwise, we keep the side effects in place (as described above) but
|
|
// just store the key in a temporary so we can refer to it later.
|
|
ref := p.generateTempRef(tempRefNeedsDeclare, "")
|
|
prop.Key = js_ast.Assign(js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}}, prop.Key)
|
|
p.recordUsage(ref)
|
|
|
|
// Use this temporary when creating duplicate references to this key
|
|
if propertyKeyTempRefs == nil {
|
|
propertyKeyTempRefs = make(map[int]ast.Ref)
|
|
}
|
|
propertyKeyTempRefs[propIndex] = ref
|
|
|
|
// Deliberately continue to fall through to the "computed" case below:
|
|
}
|
|
|
|
// Otherwise, this computed property could be a good location to evaluate
|
|
// something that comes before it. Remember this location for later.
|
|
if prop.Flags.Has(js_ast.PropertyIsComputed) {
|
|
// If any side effects after this were hoisted here, then inline them now.
|
|
// We don't want to reorder any side effects.
|
|
if ctx.computedPropertyChain.Data != nil {
|
|
ref, ok := propertyKeyTempRefs[propIndex]
|
|
if !ok {
|
|
ref = p.generateTempRef(tempRefNeedsDeclare, "")
|
|
prop.Key = js_ast.Assign(js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}}, prop.Key)
|
|
p.recordUsage(ref)
|
|
}
|
|
prop.Key = js_ast.JoinWithComma(
|
|
js_ast.JoinWithComma(prop.Key, ctx.computedPropertyChain),
|
|
js_ast.Expr{Loc: prop.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}})
|
|
p.recordUsage(ref)
|
|
ctx.computedPropertyChain = js_ast.Expr{}
|
|
}
|
|
|
|
// Remember this location for later
|
|
nextComputedPropertyKey = &prop.Key
|
|
}
|
|
}
|
|
|
|
// If any side effects in the class body were hoisted up to the "extends"
|
|
// clause, then inline them before the "extends" clause is evaluated. We
|
|
// don't want to reorder any side effects. For example:
|
|
//
|
|
// class Foo extends a() {
|
|
// static [b()]
|
|
// }
|
|
//
|
|
// We want to do this:
|
|
//
|
|
// var _a, _b;
|
|
// class Foo extends (_b = a(), _a = b(), _b) {
|
|
// }
|
|
// __publicField(Foo, _a);
|
|
//
|
|
// instead of this:
|
|
//
|
|
// var _a;
|
|
// _a = b();
|
|
// class Foo extends a() {
|
|
// }
|
|
// __publicField(Foo, _a);
|
|
//
|
|
if ctx.computedPropertyChain.Data != nil && ctx.class.ExtendsOrNil.Data != nil {
|
|
ctx.extendsRef = p.generateTempRef(tempRefNeedsDeclare, "")
|
|
ctx.class.ExtendsOrNil = js_ast.JoinWithComma(js_ast.JoinWithComma(
|
|
js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}}, ctx.class.ExtendsOrNil),
|
|
ctx.computedPropertyChain),
|
|
js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}})
|
|
p.recordUsage(ctx.extendsRef)
|
|
p.recordUsage(ctx.extendsRef)
|
|
ctx.computedPropertyChain = js_ast.Expr{}
|
|
}
|
|
return
|
|
}
|
|
|
|
// This corresponds to the initialization order in the specification:
|
|
//
|
|
// 27. For each element e of staticElements, do
|
|
// a. If e is a ClassElementDefinition Record and e.[[Kind]] is not field, then
|
|
//
|
|
// 28. For each element e of instanceElements, do
|
|
// a. If e.[[Kind]] is not field, then
|
|
//
|
|
// 29. For each element e of staticElements, do
|
|
// a. If e.[[Kind]] is field, then
|
|
//
|
|
// 30. For each element e of instanceElements, do
|
|
// a. If e.[[Kind]] is field, then
|
|
func fieldOrAccessorOrder(kind js_ast.PropertyKind, flags js_ast.PropertyFlags) (int, bool) {
|
|
if kind == js_ast.PropertyAutoAccessor {
|
|
if flags.Has(js_ast.PropertyIsStatic) {
|
|
return 0, true
|
|
} else {
|
|
return 1, true
|
|
}
|
|
} else if kind == js_ast.PropertyField {
|
|
if flags.Has(js_ast.PropertyIsStatic) {
|
|
return 2, true
|
|
} else {
|
|
return 3, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (ctx *lowerClassContext) processProperties(p *parser, classLoweringInfo classLoweringInfo, result visitClassResult) {
|
|
properties := make([]js_ast.Property, 0, len(ctx.class.Properties))
|
|
propertyKeyTempRefs, decoratorTempRefs := ctx.hoistComputedProperties(p, classLoweringInfo)
|
|
|
|
// Save the initializer index for each field and accessor element
|
|
if ctx.class.ShouldLowerStandardDecorators {
|
|
var counts [4]int
|
|
|
|
// Count how many initializers there are in each section
|
|
for _, prop := range ctx.class.Properties {
|
|
if len(prop.Decorators) > 0 {
|
|
if i, ok := fieldOrAccessorOrder(prop.Kind, prop.Flags); ok {
|
|
counts[i]++
|
|
} else if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
ctx.decoratorCallStaticMethodExtraInitializers = true
|
|
} else {
|
|
ctx.decoratorCallInstanceMethodExtraInitializers = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Give each on an index for the order it will be initialized in
|
|
if counts[0] > 0 || counts[1] > 0 || counts[2] > 0 || counts[3] > 0 {
|
|
indices := [4]int{0, counts[0], counts[0] + counts[1], counts[0] + counts[1] + counts[2]}
|
|
ctx.decoratorPropertyToInitializerMap = make(map[int]int)
|
|
|
|
for propIndex, prop := range ctx.class.Properties {
|
|
if len(prop.Decorators) > 0 {
|
|
if i, ok := fieldOrAccessorOrder(prop.Kind, prop.Flags); ok {
|
|
ctx.decoratorPropertyToInitializerMap[propIndex] = indices[i]
|
|
indices[i]++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Evaluate the decorator expressions inline
|
|
if ctx.class.ShouldLowerStandardDecorators && len(ctx.class.Decorators) > 0 {
|
|
name := ctx.nameToKeep
|
|
if name == "" {
|
|
name = "class"
|
|
}
|
|
decoratorsRef := p.generateTempRef(tempRefNeedsDeclare, fmt.Sprintf("_%s_decorators", name))
|
|
values := make([]js_ast.Expr, len(ctx.class.Decorators))
|
|
for i, decorator := range ctx.class.Decorators {
|
|
values[i] = decorator.Value
|
|
}
|
|
atLoc := ctx.class.Decorators[0].AtLoc
|
|
ctx.computedPropertyChain = js_ast.JoinWithComma(js_ast.Assign(
|
|
js_ast.Expr{Loc: atLoc, Data: &js_ast.EIdentifier{Ref: decoratorsRef}},
|
|
js_ast.Expr{Loc: atLoc, Data: &js_ast.EArray{Items: values, IsSingleLine: true}},
|
|
), ctx.computedPropertyChain)
|
|
p.recordUsage(decoratorsRef)
|
|
ctx.decoratorClassDecorators = js_ast.Expr{Loc: atLoc, Data: &js_ast.EIdentifier{Ref: decoratorsRef}}
|
|
p.recordUsage(decoratorsRef)
|
|
ctx.class.Decorators = nil
|
|
}
|
|
|
|
for propIndex, prop := range ctx.class.Properties {
|
|
if prop.Kind == js_ast.PropertyClassStaticBlock {
|
|
// Drop empty class blocks when minifying
|
|
if p.options.minifySyntax && len(prop.ClassStaticBlock.Block.Stmts) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Lower this block if needed
|
|
if classLoweringInfo.lowerAllStaticFields {
|
|
ctx.lowerStaticBlock(p, prop.Loc, *prop.ClassStaticBlock)
|
|
continue
|
|
}
|
|
|
|
// Otherwise, keep this property
|
|
properties = append(properties, prop)
|
|
continue
|
|
}
|
|
|
|
// Merge parameter decorators with method decorators
|
|
if p.options.ts.Parse && prop.Kind.IsMethodDefinition() {
|
|
if fn, ok := prop.ValueOrNil.Data.(*js_ast.EFunction); ok {
|
|
isConstructor := false
|
|
if key, ok := prop.Key.Data.(*js_ast.EString); ok {
|
|
isConstructor = helpers.UTF16EqualsString(key.Value, "constructor")
|
|
}
|
|
args := fn.Fn.Args
|
|
for i, arg := range args {
|
|
for _, decorator := range arg.Decorators {
|
|
// Generate a call to "__decorateParam()" for this parameter decorator
|
|
var decorators *[]js_ast.Decorator = &prop.Decorators
|
|
if isConstructor {
|
|
decorators = &ctx.class.Decorators
|
|
}
|
|
*decorators = append(*decorators, js_ast.Decorator{
|
|
Value: p.callRuntime(decorator.Value.Loc, "__decorateParam", []js_ast.Expr{
|
|
{Loc: decorator.Value.Loc, Data: &js_ast.ENumber{Value: float64(i)}},
|
|
decorator.Value,
|
|
}),
|
|
AtLoc: decorator.AtLoc,
|
|
})
|
|
args[i].Decorators = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
analysis := ctx.analyzeProperty(p, prop, classLoweringInfo)
|
|
|
|
// When the property key needs to be referenced multiple times, subsequent
|
|
// references may need to reference a temporary variable instead of copying
|
|
// the whole property key expression (since we only want to evaluate side
|
|
// effects once).
|
|
keyExprNoSideEffects := prop.Key
|
|
if ref, ok := propertyKeyTempRefs[propIndex]; ok {
|
|
keyExprNoSideEffects.Data = &js_ast.EIdentifier{Ref: ref}
|
|
}
|
|
|
|
// Handle TypeScript experimental decorators
|
|
if len(analysis.propExperimentalDecorators) > 0 {
|
|
prop.Decorators = nil
|
|
|
|
// Generate a single call to "__decorateClass()" for this property
|
|
loc := prop.Key.Loc
|
|
|
|
// This code tells "__decorateClass()" if the descriptor should be undefined
|
|
descriptorKind := float64(1)
|
|
if prop.Kind == js_ast.PropertyField || prop.Kind == js_ast.PropertyDeclareOrAbstract {
|
|
descriptorKind = 2
|
|
}
|
|
|
|
// Instance properties use the prototype, static properties use the class
|
|
var target js_ast.Expr
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
target = ctx.nameFunc()
|
|
} else {
|
|
target = js_ast.Expr{Loc: loc, Data: &js_ast.EDot{Target: ctx.nameFunc(), Name: "prototype", NameLoc: loc}}
|
|
}
|
|
|
|
values := make([]js_ast.Expr, len(analysis.propExperimentalDecorators))
|
|
for i, decorator := range analysis.propExperimentalDecorators {
|
|
values[i] = decorator.Value
|
|
}
|
|
decorator := p.callRuntime(loc, "__decorateClass", []js_ast.Expr{
|
|
{Loc: loc, Data: &js_ast.EArray{Items: values}},
|
|
target,
|
|
cloneKeyForLowerClass(keyExprNoSideEffects),
|
|
{Loc: loc, Data: &js_ast.ENumber{Value: descriptorKind}},
|
|
})
|
|
|
|
// Static decorators are grouped after instance decorators
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
ctx.staticExperimentalDecorators = append(ctx.staticExperimentalDecorators, decorator)
|
|
} else {
|
|
ctx.instanceExperimentalDecorators = append(ctx.instanceExperimentalDecorators, decorator)
|
|
}
|
|
}
|
|
|
|
// Handle JavaScript decorators
|
|
initializerIndex := -1
|
|
if len(analysis.propDecorators) > 0 {
|
|
prop.Decorators = nil
|
|
loc := prop.Loc
|
|
keyLoc := prop.Key.Loc
|
|
atLoc := analysis.propDecorators[0].AtLoc
|
|
|
|
// Encode information about this property using bit flags
|
|
var flags int
|
|
switch prop.Kind {
|
|
case js_ast.PropertyMethod:
|
|
flags = 1
|
|
case js_ast.PropertyGetter:
|
|
flags = 2
|
|
case js_ast.PropertySetter:
|
|
flags = 3
|
|
case js_ast.PropertyAutoAccessor:
|
|
flags = 4
|
|
case js_ast.PropertyField:
|
|
flags = 5
|
|
}
|
|
if flags >= 4 {
|
|
initializerIndex = ctx.decoratorPropertyToInitializerMap[propIndex]
|
|
}
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
flags |= 8
|
|
}
|
|
if analysis.private != nil {
|
|
flags |= 16
|
|
}
|
|
|
|
// Start the arguments for the call to "__decorateElement"
|
|
var key js_ast.Expr
|
|
decoratorsRef := decoratorTempRefs[propIndex]
|
|
if ctx.decoratorContextRef == ast.InvalidRef {
|
|
ctx.decoratorContextRef = p.generateTempRef(tempRefNeedsDeclare, "_init")
|
|
}
|
|
if analysis.private != nil {
|
|
key = js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(p.symbols[analysis.private.Ref.InnerIndex].OriginalName)}}
|
|
} else {
|
|
key = cloneKeyForLowerClass(keyExprNoSideEffects)
|
|
}
|
|
args := []js_ast.Expr{
|
|
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: loc, Data: &js_ast.ENumber{Value: float64(flags)}},
|
|
key,
|
|
{Loc: atLoc, Data: &js_ast.EIdentifier{Ref: decoratorsRef}},
|
|
}
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
p.recordUsage(decoratorsRef)
|
|
|
|
// Append any optional additional arguments
|
|
privateFnRef := ast.InvalidRef
|
|
if analysis.private != nil {
|
|
// Add the "target" argument (the weak set)
|
|
args = append(args, js_ast.Expr{Loc: keyLoc, Data: &js_ast.EIdentifier{Ref: analysis.private.Ref}})
|
|
p.recordUsage(analysis.private.Ref)
|
|
|
|
// Add the "extra" argument (the function)
|
|
switch prop.Kind {
|
|
case js_ast.PropertyMethod:
|
|
privateFnRef = p.privateGetters[analysis.private.Ref]
|
|
case js_ast.PropertyGetter:
|
|
privateFnRef = p.privateGetters[analysis.private.Ref]
|
|
case js_ast.PropertySetter:
|
|
privateFnRef = p.privateSetters[analysis.private.Ref]
|
|
}
|
|
if privateFnRef != ast.InvalidRef {
|
|
args = append(args, js_ast.Expr{Loc: keyLoc, Data: &js_ast.EIdentifier{Ref: privateFnRef}})
|
|
p.recordUsage(privateFnRef)
|
|
}
|
|
} else {
|
|
// Add the "target" argument (the class object)
|
|
args = append(args, ctx.nameFunc())
|
|
}
|
|
|
|
// Auto-accessors will generate a private field for storage. Lower this
|
|
// field, which will generate a WeakMap instance, and then pass the
|
|
// WeakMap instance into the decorator helper so the lowered getter and
|
|
// setter can use it.
|
|
if prop.Kind == js_ast.PropertyAutoAccessor {
|
|
var kind ast.SymbolKind
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
kind = ast.SymbolPrivateStaticField
|
|
} else {
|
|
kind = ast.SymbolPrivateField
|
|
}
|
|
ref := p.newSymbol(kind, "#"+p.propertyNameHint(prop.Key))
|
|
p.symbols[ref.InnerIndex].Flags |= ast.PrivateSymbolMustBeLowered
|
|
_, autoAccessorWeakMapRef, _ := ctx.lowerField(p, prop, &js_ast.EPrivateIdentifier{Ref: ref}, false, false, initializerIndex)
|
|
args = append(args, js_ast.Expr{Loc: keyLoc, Data: &js_ast.EIdentifier{Ref: autoAccessorWeakMapRef}})
|
|
p.recordUsage(autoAccessorWeakMapRef)
|
|
}
|
|
|
|
// Assign the result
|
|
element := p.callRuntime(loc, "__decorateElement", args)
|
|
if privateFnRef != ast.InvalidRef {
|
|
element = js_ast.Assign(js_ast.Expr{Loc: keyLoc, Data: &js_ast.EIdentifier{Ref: privateFnRef}}, element)
|
|
p.recordUsage(privateFnRef)
|
|
} else if prop.Kind == js_ast.PropertyAutoAccessor && analysis.private != nil {
|
|
ref := p.generateTempRef(tempRefNeedsDeclare, "")
|
|
privateGetFnRef := p.generateTempRef(tempRefNeedsDeclare, "_")
|
|
privateSetFnRef := p.generateTempRef(tempRefNeedsDeclare, "_")
|
|
p.symbols[privateGetFnRef.InnerIndex].Link = p.privateGetters[analysis.private.Ref]
|
|
p.symbols[privateSetFnRef.InnerIndex].Link = p.privateSetters[analysis.private.Ref]
|
|
|
|
// Unpack the "get" and "set" properties from the returned property descriptor
|
|
element = js_ast.JoinWithComma(js_ast.JoinWithComma(
|
|
js_ast.Assign(
|
|
js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}},
|
|
element),
|
|
js_ast.Assign(
|
|
js_ast.Expr{Loc: keyLoc, Data: &js_ast.EIdentifier{Ref: privateGetFnRef}},
|
|
js_ast.Expr{Loc: loc, Data: &js_ast.EDot{Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}, Name: "get", NameLoc: loc}})),
|
|
js_ast.Assign(
|
|
js_ast.Expr{Loc: keyLoc, Data: &js_ast.EIdentifier{Ref: privateSetFnRef}},
|
|
js_ast.Expr{Loc: loc, Data: &js_ast.EDot{Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}, Name: "set", NameLoc: loc}}))
|
|
p.recordUsage(ref)
|
|
p.recordUsage(privateGetFnRef)
|
|
p.recordUsage(ref)
|
|
p.recordUsage(privateSetFnRef)
|
|
p.recordUsage(ref)
|
|
}
|
|
|
|
// Put the call to the decorators in the right place
|
|
if prop.Kind == js_ast.PropertyField {
|
|
// Field
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
ctx.decoratorStaticFieldElements = append(ctx.decoratorStaticFieldElements, element)
|
|
} else {
|
|
ctx.decoratorInstanceFieldElements = append(ctx.decoratorInstanceFieldElements, element)
|
|
}
|
|
} else {
|
|
// Non-field
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
ctx.decoratorStaticNonFieldElements = append(ctx.decoratorStaticNonFieldElements, element)
|
|
} else {
|
|
ctx.decoratorInstanceNonFieldElements = append(ctx.decoratorInstanceNonFieldElements, element)
|
|
}
|
|
}
|
|
|
|
// Omit decorated auto-accessors as they will be now generated at run-time instead
|
|
if prop.Kind == js_ast.PropertyAutoAccessor {
|
|
if analysis.private != nil {
|
|
ctx.lowerPrivateMethod(p, prop, analysis.private)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Generate get/set methods for auto-accessors
|
|
if analysis.rewriteAutoAccessorToGetSet {
|
|
properties = ctx.rewriteAutoAccessorToGetSet(p, prop, properties, keyExprNoSideEffects, analysis.mustLowerField, analysis.private, result)
|
|
continue
|
|
}
|
|
|
|
// Lower fields
|
|
if (!prop.Kind.IsMethodDefinition() && analysis.mustLowerField) || analysis.staticFieldToBlockAssign {
|
|
var keep bool
|
|
prop, _, keep = ctx.lowerField(p, prop, analysis.private, analysis.shouldOmitFieldInitializer, analysis.staticFieldToBlockAssign, initializerIndex)
|
|
if !keep {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Lower methods
|
|
if prop.Kind.IsMethodDefinition() && ctx.lowerMethod(p, prop, analysis.private) {
|
|
continue
|
|
}
|
|
|
|
// Keep this property
|
|
properties = append(properties, prop)
|
|
}
|
|
|
|
// Finish the filtering operation
|
|
ctx.class.Properties = properties
|
|
}
|
|
|
|
func (ctx *lowerClassContext) lowerStaticBlock(p *parser, loc logger.Loc, block js_ast.ClassStaticBlock) {
|
|
isAllExprs := []js_ast.Expr{}
|
|
|
|
// Are all statements in the block expression statements?
|
|
loop:
|
|
for _, stmt := range block.Block.Stmts {
|
|
switch s := stmt.Data.(type) {
|
|
case *js_ast.SEmpty:
|
|
// Omit stray semicolons completely
|
|
case *js_ast.SExpr:
|
|
isAllExprs = append(isAllExprs, s.Value)
|
|
default:
|
|
isAllExprs = nil
|
|
break loop
|
|
}
|
|
}
|
|
|
|
if isAllExprs != nil {
|
|
// I think it should be safe to inline the static block IIFE here
|
|
// since all uses of "this" should have already been replaced by now.
|
|
ctx.staticMembers = append(ctx.staticMembers, isAllExprs...)
|
|
} else {
|
|
// But if there is a non-expression statement, fall back to using an
|
|
// IIFE since we may be in an expression context and can't use a block.
|
|
ctx.staticMembers = append(ctx.staticMembers, js_ast.Expr{Loc: loc, Data: &js_ast.ECall{
|
|
Target: js_ast.Expr{Loc: loc, Data: &js_ast.EArrow{Body: js_ast.FnBody{
|
|
Loc: block.Loc,
|
|
Block: block.Block,
|
|
}}},
|
|
CanBeUnwrappedIfUnused: p.astHelpers.StmtsCanBeRemovedIfUnused(block.Block.Stmts, 0),
|
|
}})
|
|
}
|
|
}
|
|
|
|
func (ctx *lowerClassContext) rewriteAutoAccessorToGetSet(
|
|
p *parser,
|
|
prop js_ast.Property,
|
|
properties []js_ast.Property,
|
|
keyExprNoSideEffects js_ast.Expr,
|
|
mustLowerField bool,
|
|
private *js_ast.EPrivateIdentifier,
|
|
result visitClassResult,
|
|
) []js_ast.Property {
|
|
var storageKind ast.SymbolKind
|
|
if prop.Flags.Has(js_ast.PropertyIsStatic) {
|
|
storageKind = ast.SymbolPrivateStaticField
|
|
} else {
|
|
storageKind = ast.SymbolPrivateField
|
|
}
|
|
|
|
// Generate the name of the private field to use for storage
|
|
var storageName string
|
|
switch k := keyExprNoSideEffects.Data.(type) {
|
|
case *js_ast.EString:
|
|
storageName = "#" + helpers.UTF16ToString(k.Value)
|
|
case *js_ast.EPrivateIdentifier:
|
|
storageName = "#_" + p.symbols[k.Ref.InnerIndex].OriginalName[1:]
|
|
default:
|
|
storageName = "#" + ast.DefaultNameMinifierJS.NumberToMinifiedName(ctx.autoAccessorCount)
|
|
ctx.autoAccessorCount++
|
|
}
|
|
|
|
// Generate the symbols we need
|
|
storageRef := p.newSymbol(storageKind, storageName)
|
|
argRef := p.newSymbol(ast.SymbolOther, "_")
|
|
result.bodyScope.Generated = append(result.bodyScope.Generated, storageRef)
|
|
result.bodyScope.Children = append(result.bodyScope.Children, &js_ast.Scope{Kind: js_ast.ScopeFunctionBody, Generated: []ast.Ref{argRef}})
|
|
|
|
// Replace this accessor with other properties
|
|
loc := keyExprNoSideEffects.Loc
|
|
storagePrivate := &js_ast.EPrivateIdentifier{Ref: storageRef}
|
|
if mustLowerField {
|
|
// Forward the accessor's lowering status on to the storage field. If we
|
|
// don't do this, then we risk having the underlying private symbol
|
|
// behaving differently than if it were authored manually (e.g. being
|
|
// placed outside of the class body, which is a syntax error).
|
|
p.symbols[storageRef.InnerIndex].Flags |= ast.PrivateSymbolMustBeLowered
|
|
}
|
|
storageNeedsToBeLowered := p.privateSymbolNeedsToBeLowered(storagePrivate)
|
|
storageProp := js_ast.Property{
|
|
Loc: prop.Loc,
|
|
Kind: js_ast.PropertyField,
|
|
Flags: prop.Flags & js_ast.PropertyIsStatic,
|
|
Key: js_ast.Expr{Loc: loc, Data: storagePrivate},
|
|
InitializerOrNil: prop.InitializerOrNil,
|
|
}
|
|
if !mustLowerField {
|
|
properties = append(properties, storageProp)
|
|
} else if prop, _, ok := ctx.lowerField(p, storageProp, storagePrivate, false, false, -1); ok {
|
|
properties = append(properties, prop)
|
|
}
|
|
|
|
// Getter
|
|
var getExpr js_ast.Expr
|
|
if storageNeedsToBeLowered {
|
|
getExpr = p.lowerPrivateGet(js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}, loc, storagePrivate)
|
|
} else {
|
|
p.recordUsage(storageRef)
|
|
getExpr = js_ast.Expr{Loc: loc, Data: &js_ast.EIndex{
|
|
Target: js_ast.Expr{Loc: loc, Data: js_ast.EThisShared},
|
|
Index: js_ast.Expr{Loc: loc, Data: &js_ast.EPrivateIdentifier{Ref: storageRef}},
|
|
}}
|
|
}
|
|
getterProp := js_ast.Property{
|
|
Loc: prop.Loc,
|
|
Kind: js_ast.PropertyGetter,
|
|
Flags: prop.Flags,
|
|
Key: prop.Key,
|
|
ValueOrNil: js_ast.Expr{Loc: loc, Data: &js_ast.EFunction{
|
|
Fn: js_ast.Fn{
|
|
Body: js_ast.FnBody{
|
|
Loc: loc,
|
|
Block: js_ast.SBlock{
|
|
Stmts: []js_ast.Stmt{
|
|
{Loc: loc, Data: &js_ast.SReturn{ValueOrNil: getExpr}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
if !ctx.lowerMethod(p, getterProp, private) {
|
|
properties = append(properties, getterProp)
|
|
}
|
|
|
|
// Setter
|
|
var setExpr js_ast.Expr
|
|
if storageNeedsToBeLowered {
|
|
setExpr = p.lowerPrivateSet(js_ast.Expr{Loc: loc, Data: js_ast.EThisShared}, loc, storagePrivate,
|
|
js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: argRef}})
|
|
} else {
|
|
p.recordUsage(storageRef)
|
|
p.recordUsage(argRef)
|
|
setExpr = js_ast.Assign(
|
|
js_ast.Expr{Loc: loc, Data: &js_ast.EIndex{
|
|
Target: js_ast.Expr{Loc: loc, Data: js_ast.EThisShared},
|
|
Index: js_ast.Expr{Loc: loc, Data: &js_ast.EPrivateIdentifier{Ref: storageRef}},
|
|
}},
|
|
js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: argRef}},
|
|
)
|
|
}
|
|
setterProp := js_ast.Property{
|
|
Loc: prop.Loc,
|
|
Kind: js_ast.PropertySetter,
|
|
Flags: prop.Flags,
|
|
Key: cloneKeyForLowerClass(keyExprNoSideEffects),
|
|
ValueOrNil: js_ast.Expr{Loc: loc, Data: &js_ast.EFunction{
|
|
Fn: js_ast.Fn{
|
|
Args: []js_ast.Arg{
|
|
{Binding: js_ast.Binding{Loc: loc, Data: &js_ast.BIdentifier{Ref: argRef}}},
|
|
},
|
|
Body: js_ast.FnBody{
|
|
Loc: loc,
|
|
Block: js_ast.SBlock{
|
|
Stmts: []js_ast.Stmt{
|
|
{Loc: loc, Data: &js_ast.SExpr{Value: setExpr}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
if !ctx.lowerMethod(p, setterProp, private) {
|
|
properties = append(properties, setterProp)
|
|
}
|
|
return properties
|
|
}
|
|
|
|
func (ctx *lowerClassContext) insertInitializersIntoConstructor(p *parser, classLoweringInfo classLoweringInfo, result visitClassResult) {
|
|
if len(ctx.parameterFieldProps) > 0 {
|
|
ctx.class.Properties = append(ctx.parameterFieldProps, ctx.class.Properties...)
|
|
}
|
|
|
|
if len(ctx.parameterFields) == 0 &&
|
|
!ctx.decoratorCallInstanceMethodExtraInitializers &&
|
|
len(ctx.instancePrivateMethods) == 0 &&
|
|
len(ctx.instanceMembers) == 0 &&
|
|
(ctx.ctor == nil || result.superCtorRef == ast.InvalidRef) {
|
|
// No need to generate a constructor
|
|
return
|
|
}
|
|
|
|
// Create a constructor if one doesn't already exist
|
|
if ctx.ctor == nil {
|
|
ctx.ctor = &js_ast.EFunction{Fn: js_ast.Fn{Body: js_ast.FnBody{Loc: ctx.classLoc}}}
|
|
|
|
// Append it to the list to reuse existing allocation space
|
|
ctx.class.Properties = append(ctx.class.Properties, js_ast.Property{
|
|
Kind: js_ast.PropertyMethod,
|
|
Loc: ctx.classLoc,
|
|
Key: js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EString{Value: helpers.StringToUTF16("constructor")}},
|
|
ValueOrNil: js_ast.Expr{Loc: ctx.classLoc, Data: ctx.ctor},
|
|
})
|
|
|
|
// Make sure the constructor has a super() call if needed
|
|
if ctx.class.ExtendsOrNil.Data != nil {
|
|
target := js_ast.Expr{Loc: ctx.classLoc, Data: js_ast.ESuperShared}
|
|
if classLoweringInfo.shimSuperCtorCalls {
|
|
p.recordUsage(result.superCtorRef)
|
|
target.Data = &js_ast.EIdentifier{Ref: result.superCtorRef}
|
|
}
|
|
argumentsRef := p.newSymbol(ast.SymbolUnbound, "arguments")
|
|
p.currentScope.Generated = append(p.currentScope.Generated, argumentsRef)
|
|
ctx.ctor.Fn.Body.Block.Stmts = append(ctx.ctor.Fn.Body.Block.Stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.ECall{
|
|
Target: target,
|
|
Args: []js_ast.Expr{{Loc: ctx.classLoc, Data: &js_ast.ESpread{Value: js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: argumentsRef}}}}},
|
|
}}}})
|
|
}
|
|
}
|
|
|
|
// Run instanceMethodExtraInitializers if needed
|
|
var decoratorInstanceMethodExtraInitializers js_ast.Expr
|
|
if ctx.decoratorCallInstanceMethodExtraInitializers {
|
|
decoratorInstanceMethodExtraInitializers = p.callRuntime(ctx.classLoc, "__runInitializers", []js_ast.Expr{
|
|
{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: ctx.classLoc, Data: &js_ast.ENumber{Value: (2 << 1) | 1}},
|
|
{Loc: ctx.classLoc, Data: js_ast.EThisShared},
|
|
})
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
}
|
|
|
|
// Make sure the instance field initializers come after "super()" since
|
|
// they need "this" to ba available
|
|
generatedStmts := make([]js_ast.Stmt, 0,
|
|
len(ctx.parameterFields)+
|
|
len(ctx.instancePrivateMethods)+
|
|
len(ctx.instanceMembers))
|
|
generatedStmts = append(generatedStmts, ctx.parameterFields...)
|
|
if decoratorInstanceMethodExtraInitializers.Data != nil {
|
|
generatedStmts = append(generatedStmts, js_ast.Stmt{Loc: decoratorInstanceMethodExtraInitializers.Loc, Data: &js_ast.SExpr{Value: decoratorInstanceMethodExtraInitializers}})
|
|
}
|
|
generatedStmts = append(generatedStmts, ctx.instancePrivateMethods...)
|
|
generatedStmts = append(generatedStmts, ctx.instanceMembers...)
|
|
p.insertStmtsAfterSuperCall(&ctx.ctor.Fn.Body, generatedStmts, result.superCtorRef)
|
|
|
|
// Sort the constructor first to match the TypeScript compiler's output
|
|
for i := 0; i < len(ctx.class.Properties); i++ {
|
|
if ctx.class.Properties[i].ValueOrNil.Data == ctx.ctor {
|
|
ctorProp := ctx.class.Properties[i]
|
|
for j := i; j > 0; j-- {
|
|
ctx.class.Properties[j] = ctx.class.Properties[j-1]
|
|
}
|
|
ctx.class.Properties[0] = ctorProp
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ctx *lowerClassContext) finishAndGenerateCode(p *parser, result visitClassResult) ([]js_ast.Stmt, js_ast.Expr) {
|
|
// When bundling is enabled, we convert top-level class statements to
|
|
// expressions:
|
|
//
|
|
// // Before
|
|
// class Foo {
|
|
// static foo = () => Foo
|
|
// }
|
|
// Foo = wrap(Foo)
|
|
//
|
|
// // After
|
|
// var _Foo = class _Foo {
|
|
// static foo = () => _Foo;
|
|
// };
|
|
// var Foo = _Foo;
|
|
// Foo = wrap(Foo);
|
|
//
|
|
// One reason to do this is that esbuild's bundler sometimes needs to lazily-
|
|
// evaluate a module. For example, a module may end up being both the target
|
|
// of a dynamic "import()" call and a static "import" statement. Lazy module
|
|
// evaluation is done by wrapping the top-level module code in a closure. To
|
|
// avoid a performance hit for static "import" statements, esbuild stores
|
|
// top-level exported symbols outside of the closure and references them
|
|
// directly instead of indirectly.
|
|
//
|
|
// Another reason to do this is that multiple JavaScript VMs have had and
|
|
// continue to have performance issues with TDZ (i.e. "temporal dead zone")
|
|
// checks. These checks validate that a let, or const, or class symbol isn't
|
|
// used before it's initialized. Here are two issues with well-known VMs:
|
|
//
|
|
// * V8: https://bugs.chromium.org/p/v8/issues/detail?id=13723 (10% slowdown)
|
|
// * JavaScriptCore: https://bugs.webkit.org/show_bug.cgi?id=199866 (1,000% slowdown!)
|
|
//
|
|
// JavaScriptCore had a severe performance issue as their TDZ implementation
|
|
// had time complexity that was quadratic in the number of variables needing
|
|
// TDZ checks in the same scope (with the top-level scope typically being the
|
|
// worst offender). V8 has ongoing issues with TDZ checks being present
|
|
// throughout the code their JIT generates even when they have already been
|
|
// checked earlier in the same function or when the function in question has
|
|
// already been run (so the checks have already happened).
|
|
//
|
|
// Due to esbuild's parallel architecture, we both a) need to transform class
|
|
// statements to variables during parsing and b) don't yet know whether this
|
|
// module will need to be lazily-evaluated or not in the parser. So we always
|
|
// do this just in case it's needed.
|
|
mustConvertStmtToExpr := ctx.kind != classKindExpr && p.currentScope.Parent == nil && (p.options.mode == config.ModeBundle || p.willWrapModuleInTryCatchForUsing)
|
|
|
|
// Check to see if we have lowered decorators on the class itself
|
|
var classDecorators js_ast.Expr
|
|
var classExperimentalDecorators []js_ast.Decorator
|
|
if p.options.ts.Parse && p.options.ts.Config.ExperimentalDecorators == config.True {
|
|
classExperimentalDecorators = ctx.class.Decorators
|
|
ctx.class.Decorators = nil
|
|
} else if ctx.class.ShouldLowerStandardDecorators {
|
|
classDecorators = ctx.decoratorClassDecorators
|
|
}
|
|
|
|
var decorateClassExpr js_ast.Expr
|
|
if classDecorators.Data != nil {
|
|
// Handle JavaScript decorators on the class itself
|
|
if ctx.decoratorContextRef == ast.InvalidRef {
|
|
ctx.decoratorContextRef = p.generateTempRef(tempRefNeedsDeclare, "_init")
|
|
}
|
|
decorateClassExpr = p.callRuntime(ctx.classLoc, "__decorateElement", []js_ast.Expr{
|
|
{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: ctx.classLoc, Data: &js_ast.ENumber{Value: 0}},
|
|
{Loc: ctx.classLoc, Data: &js_ast.EString{Value: helpers.StringToUTF16(ctx.nameToKeep)}},
|
|
classDecorators,
|
|
ctx.nameFunc(),
|
|
})
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
decorateClassExpr = js_ast.Assign(ctx.nameFunc(), decorateClassExpr)
|
|
} else if ctx.decoratorContextRef != ast.InvalidRef {
|
|
// Decorator metadata is present if there are any decorators on the class at all
|
|
decorateClassExpr = p.callRuntime(ctx.classLoc, "__decoratorMetadata", []js_ast.Expr{
|
|
{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
ctx.nameFunc(),
|
|
})
|
|
}
|
|
|
|
// If this is true, we have removed some code from the class body that could
|
|
// potentially contain an expression that captures the inner class name.
|
|
// In this case we must explicitly store the class to a separate inner class
|
|
// name binding to avoid incorrect behavior if the class is later re-assigned,
|
|
// since the removed code will no longer be in the class body scope.
|
|
hasPotentialInnerClassNameEscape := result.innerClassNameRef != ast.InvalidRef &&
|
|
(ctx.computedPropertyChain.Data != nil ||
|
|
len(ctx.privateMembers) > 0 ||
|
|
len(ctx.staticPrivateMethods) > 0 ||
|
|
len(ctx.staticMembers) > 0 ||
|
|
|
|
// TypeScript experimental decorators
|
|
len(ctx.instanceExperimentalDecorators) > 0 ||
|
|
len(ctx.staticExperimentalDecorators) > 0 ||
|
|
len(classExperimentalDecorators) > 0 ||
|
|
|
|
// JavaScript decorators
|
|
ctx.decoratorContextRef != ast.InvalidRef)
|
|
|
|
// If we need to represent the class as an expression (even if it's a
|
|
// statement), then generate another symbol to use as the class name
|
|
nameForClassDecorators := ast.LocRef{Ref: ast.InvalidRef}
|
|
if len(classExperimentalDecorators) > 0 || hasPotentialInnerClassNameEscape || mustConvertStmtToExpr {
|
|
if ctx.kind == classKindExpr {
|
|
// For expressions, the inner and outer class names are the same
|
|
name := ctx.nameFunc()
|
|
nameForClassDecorators = ast.LocRef{Loc: name.Loc, Ref: name.Data.(*js_ast.EIdentifier).Ref}
|
|
} else {
|
|
// For statements we need to use the outer class name, not the inner one
|
|
if ctx.class.Name != nil {
|
|
nameForClassDecorators = *ctx.class.Name
|
|
} else if ctx.kind == classKindExportDefaultStmt {
|
|
nameForClassDecorators = ctx.defaultName
|
|
} else {
|
|
nameForClassDecorators = ast.LocRef{Loc: ctx.classLoc, Ref: p.generateTempRef(tempRefNoDeclare, "")}
|
|
}
|
|
p.recordUsage(nameForClassDecorators.Ref)
|
|
}
|
|
}
|
|
|
|
var prefixExprs []js_ast.Expr
|
|
var suffixExprs []js_ast.Expr
|
|
|
|
// If there are JavaScript decorators, start by allocating a context object
|
|
if ctx.decoratorContextRef != ast.InvalidRef {
|
|
base := js_ast.Expr{Loc: ctx.classLoc, Data: js_ast.ENullShared}
|
|
if ctx.class.ExtendsOrNil.Data != nil {
|
|
if ctx.extendsRef == ast.InvalidRef {
|
|
ctx.extendsRef = p.generateTempRef(tempRefNeedsDeclare, "")
|
|
ctx.class.ExtendsOrNil = js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}}, ctx.class.ExtendsOrNil)
|
|
p.recordUsage(ctx.extendsRef)
|
|
}
|
|
base.Data = &js_ast.EIdentifier{Ref: ctx.extendsRef}
|
|
}
|
|
suffixExprs = append(suffixExprs, js_ast.Assign(
|
|
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
p.callRuntime(ctx.classLoc, "__decoratorStart", []js_ast.Expr{base}),
|
|
))
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
}
|
|
|
|
// Any of the computed property chain that we hoisted out of the class
|
|
// body needs to come before the class expression.
|
|
if ctx.computedPropertyChain.Data != nil {
|
|
prefixExprs = append(prefixExprs, ctx.computedPropertyChain)
|
|
}
|
|
|
|
// WeakSets and WeakMaps
|
|
suffixExprs = append(suffixExprs, ctx.privateMembers...)
|
|
|
|
// Evaluate JavaScript decorators here
|
|
suffixExprs = append(suffixExprs, ctx.decoratorStaticNonFieldElements...)
|
|
suffixExprs = append(suffixExprs, ctx.decoratorInstanceNonFieldElements...)
|
|
suffixExprs = append(suffixExprs, ctx.decoratorStaticFieldElements...)
|
|
suffixExprs = append(suffixExprs, ctx.decoratorInstanceFieldElements...)
|
|
|
|
// Lowered initializers for static methods (including getters and setters)
|
|
suffixExprs = append(suffixExprs, ctx.staticPrivateMethods...)
|
|
|
|
// Run JavaScript class decorators at the end of class initialization
|
|
if decorateClassExpr.Data != nil {
|
|
suffixExprs = append(suffixExprs, decorateClassExpr)
|
|
}
|
|
|
|
// For each element initializer of staticMethodExtraInitializers
|
|
if ctx.decoratorCallStaticMethodExtraInitializers {
|
|
suffixExprs = append(suffixExprs, p.callRuntime(ctx.classLoc, "__runInitializers", []js_ast.Expr{
|
|
{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: ctx.classLoc, Data: &js_ast.ENumber{Value: (1 << 1) | 1}},
|
|
ctx.nameFunc(),
|
|
}))
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
}
|
|
|
|
// Lowered initializers for static fields, static accessors, and static blocks
|
|
suffixExprs = append(suffixExprs, ctx.staticMembers...)
|
|
|
|
// The official TypeScript compiler adds generated code after the class body
|
|
// in this exact order. Matching this order is important for correctness.
|
|
suffixExprs = append(suffixExprs, ctx.instanceExperimentalDecorators...)
|
|
suffixExprs = append(suffixExprs, ctx.staticExperimentalDecorators...)
|
|
|
|
// For each element initializer of classExtraInitializers
|
|
if classDecorators.Data != nil {
|
|
suffixExprs = append(suffixExprs, p.callRuntime(ctx.classLoc, "__runInitializers", []js_ast.Expr{
|
|
{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
|
|
{Loc: ctx.classLoc, Data: &js_ast.ENumber{Value: (0 << 1) | 1}},
|
|
ctx.nameFunc(),
|
|
}))
|
|
p.recordUsage(ctx.decoratorContextRef)
|
|
}
|
|
|
|
// Run TypeScript experimental class decorators at the end of class initialization
|
|
if len(classExperimentalDecorators) > 0 {
|
|
values := make([]js_ast.Expr, len(classExperimentalDecorators))
|
|
for i, decorator := range classExperimentalDecorators {
|
|
values[i] = decorator.Value
|
|
}
|
|
suffixExprs = append(suffixExprs, js_ast.Assign(
|
|
js_ast.Expr{Loc: nameForClassDecorators.Loc, Data: &js_ast.EIdentifier{Ref: nameForClassDecorators.Ref}},
|
|
p.callRuntime(ctx.classLoc, "__decorateClass", []js_ast.Expr{
|
|
{Loc: ctx.classLoc, Data: &js_ast.EArray{Items: values}},
|
|
{Loc: nameForClassDecorators.Loc, Data: &js_ast.EIdentifier{Ref: nameForClassDecorators.Ref}},
|
|
}),
|
|
))
|
|
p.recordUsage(nameForClassDecorators.Ref)
|
|
p.recordUsage(nameForClassDecorators.Ref)
|
|
}
|
|
|
|
// Our caller expects us to return the same form that was originally given to
|
|
// us. If the class was originally an expression, then return an expression.
|
|
if ctx.kind == classKindExpr {
|
|
// Calling "nameFunc" will replace "classExpr", so make sure to do that first
|
|
// before joining "classExpr" with any other expressions
|
|
var nameToJoin js_ast.Expr
|
|
if ctx.didCaptureClassExpr || len(suffixExprs) > 0 {
|
|
nameToJoin = ctx.nameFunc()
|
|
}
|
|
|
|
// Insert expressions on either side of the class as appropriate
|
|
ctx.classExpr = js_ast.JoinWithComma(js_ast.JoinAllWithComma(prefixExprs), ctx.classExpr)
|
|
ctx.classExpr = js_ast.JoinWithComma(ctx.classExpr, js_ast.JoinAllWithComma(suffixExprs))
|
|
|
|
// Finally join "classExpr" with the variable that holds the class object
|
|
ctx.classExpr = js_ast.JoinWithComma(ctx.classExpr, nameToJoin)
|
|
if ctx.wrapFunc != nil {
|
|
ctx.classExpr = ctx.wrapFunc(ctx.classExpr)
|
|
}
|
|
return nil, ctx.classExpr
|
|
}
|
|
|
|
// Otherwise, the class was originally a statement. Return an array of
|
|
// statements instead.
|
|
var stmts []js_ast.Stmt
|
|
var outerClassNameDecl js_ast.Stmt
|
|
|
|
// Insert expressions before the class as appropriate
|
|
for _, expr := range prefixExprs {
|
|
stmts = append(stmts, js_ast.Stmt{Loc: expr.Loc, Data: &js_ast.SExpr{Value: expr}})
|
|
}
|
|
|
|
// Handle converting a class statement to a class expression
|
|
if nameForClassDecorators.Ref != ast.InvalidRef {
|
|
classExpr := js_ast.EClass{Class: *ctx.class}
|
|
ctx.class = &classExpr.Class
|
|
init := js_ast.Expr{Loc: ctx.classLoc, Data: &classExpr}
|
|
|
|
// If the inner class name was referenced, then set the name of the class
|
|
// that we will end up printing to the inner class name. Otherwise if the
|
|
// inner class name was unused, we can just leave it blank.
|
|
if result.innerClassNameRef != ast.InvalidRef {
|
|
// "class Foo { x = Foo }" => "const Foo = class _Foo { x = _Foo }"
|
|
ctx.class.Name.Ref = result.innerClassNameRef
|
|
} else {
|
|
// "class Foo {}" => "const Foo = class {}"
|
|
ctx.class.Name = nil
|
|
}
|
|
|
|
// Generate the class initialization statement
|
|
if len(classExperimentalDecorators) > 0 {
|
|
// If there are class decorators, then we actually need to mutate the
|
|
// immutable "const" binding that shadows everything in the class body.
|
|
// The official TypeScript compiler does this by rewriting all class name
|
|
// references in the class body to another temporary variable. This is
|
|
// basically what we're doing here.
|
|
p.recordUsage(nameForClassDecorators.Ref)
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SLocal{
|
|
Kind: p.selectLocalKind(js_ast.LocalLet),
|
|
IsExport: ctx.kind == classKindExportStmt,
|
|
Decls: []js_ast.Decl{{
|
|
Binding: js_ast.Binding{Loc: nameForClassDecorators.Loc, Data: &js_ast.BIdentifier{Ref: nameForClassDecorators.Ref}},
|
|
ValueOrNil: init,
|
|
}},
|
|
}})
|
|
if ctx.class.Name != nil {
|
|
p.mergeSymbols(ctx.class.Name.Ref, nameForClassDecorators.Ref)
|
|
ctx.class.Name = nil
|
|
}
|
|
} else if hasPotentialInnerClassNameEscape {
|
|
// If the inner class name was used, then we explicitly generate a binding
|
|
// for it. That means the mutable outer class name is separate, and is
|
|
// initialized after all static member initializers have finished.
|
|
captureRef := p.newSymbol(ast.SymbolOther, p.symbols[result.innerClassNameRef.InnerIndex].OriginalName)
|
|
p.currentScope.Generated = append(p.currentScope.Generated, captureRef)
|
|
p.recordDeclaredSymbol(captureRef)
|
|
p.mergeSymbols(result.innerClassNameRef, captureRef)
|
|
kind := js_ast.LocalConst
|
|
if classDecorators.Data != nil {
|
|
// Class decorators need to be able to potentially mutate this binding
|
|
kind = js_ast.LocalLet
|
|
}
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SLocal{
|
|
Kind: p.selectLocalKind(kind),
|
|
Decls: []js_ast.Decl{{
|
|
Binding: js_ast.Binding{Loc: nameForClassDecorators.Loc, Data: &js_ast.BIdentifier{Ref: captureRef}},
|
|
ValueOrNil: init,
|
|
}},
|
|
}})
|
|
p.recordUsage(nameForClassDecorators.Ref)
|
|
p.recordUsage(captureRef)
|
|
outerClassNameDecl = js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SLocal{
|
|
Kind: p.selectLocalKind(js_ast.LocalLet),
|
|
IsExport: ctx.kind == classKindExportStmt,
|
|
Decls: []js_ast.Decl{{
|
|
Binding: js_ast.Binding{Loc: nameForClassDecorators.Loc, Data: &js_ast.BIdentifier{Ref: nameForClassDecorators.Ref}},
|
|
ValueOrNil: js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: captureRef}},
|
|
}},
|
|
}}
|
|
} else {
|
|
// Otherwise, the inner class name isn't needed and we can just
|
|
// use a single variable declaration for the outer class name.
|
|
p.recordUsage(nameForClassDecorators.Ref)
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SLocal{
|
|
Kind: p.selectLocalKind(js_ast.LocalLet),
|
|
IsExport: ctx.kind == classKindExportStmt,
|
|
Decls: []js_ast.Decl{{
|
|
Binding: js_ast.Binding{Loc: nameForClassDecorators.Loc, Data: &js_ast.BIdentifier{Ref: nameForClassDecorators.Ref}},
|
|
ValueOrNil: init,
|
|
}},
|
|
}})
|
|
}
|
|
} else {
|
|
// Generate the specific kind of class statement that was passed in to us
|
|
switch ctx.kind {
|
|
case classKindStmt:
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SClass{Class: *ctx.class}})
|
|
case classKindExportStmt:
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SClass{Class: *ctx.class, IsExport: true}})
|
|
case classKindExportDefaultStmt:
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SExportDefault{
|
|
DefaultName: ctx.defaultName,
|
|
Value: js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SClass{Class: *ctx.class}},
|
|
}})
|
|
}
|
|
|
|
// The inner class name inside the class statement should be the same as
|
|
// the class statement name itself
|
|
if ctx.class.Name != nil && result.innerClassNameRef != ast.InvalidRef {
|
|
// If the class body contains a direct eval call, then the inner class
|
|
// name will be marked as "MustNotBeRenamed" (because we have already
|
|
// popped the class body scope) but the outer class name won't be marked
|
|
// as "MustNotBeRenamed" yet (because we haven't yet popped the containing
|
|
// scope). Propagate this flag now before we merge these symbols so we
|
|
// don't end up accidentally renaming the outer class name to the inner
|
|
// class name.
|
|
if p.currentScope.ContainsDirectEval {
|
|
p.symbols[ctx.class.Name.Ref.InnerIndex].Flags |= (p.symbols[result.innerClassNameRef.InnerIndex].Flags & ast.MustNotBeRenamed)
|
|
}
|
|
p.mergeSymbols(result.innerClassNameRef, ctx.class.Name.Ref)
|
|
}
|
|
}
|
|
|
|
// Insert expressions after the class as appropriate
|
|
for _, expr := range suffixExprs {
|
|
stmts = append(stmts, js_ast.Stmt{Loc: expr.Loc, Data: &js_ast.SExpr{Value: expr}})
|
|
}
|
|
|
|
// This must come after the class body initializers have finished
|
|
if outerClassNameDecl.Data != nil {
|
|
stmts = append(stmts, outerClassNameDecl)
|
|
}
|
|
|
|
if nameForClassDecorators.Ref != ast.InvalidRef && ctx.kind == classKindExportDefaultStmt {
|
|
// "export default class x {}" => "class x {} export {x as default}"
|
|
stmts = append(stmts, js_ast.Stmt{Loc: ctx.classLoc, Data: &js_ast.SExportClause{
|
|
Items: []js_ast.ClauseItem{{Alias: "default", Name: ctx.defaultName}},
|
|
}})
|
|
}
|
|
return stmts, js_ast.Expr{}
|
|
}
|
|
|
|
func cloneKeyForLowerClass(key js_ast.Expr) js_ast.Expr {
|
|
switch k := key.Data.(type) {
|
|
case *js_ast.ENumber:
|
|
clone := *k
|
|
key.Data = &clone
|
|
case *js_ast.EString:
|
|
clone := *k
|
|
key.Data = &clone
|
|
case *js_ast.EIdentifier:
|
|
clone := *k
|
|
key.Data = &clone
|
|
case *js_ast.ENameOfSymbol:
|
|
clone := *k
|
|
key.Data = &clone
|
|
case *js_ast.EPrivateIdentifier:
|
|
clone := *k
|
|
key.Data = &clone
|
|
default:
|
|
panic("Internal error")
|
|
}
|
|
return key
|
|
}
|
|
|
|
// 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 ast.Ref) {
|
|
// If this class has no base class, then there's no "super()" call to handle
|
|
if superCtorRef == ast.InvalidRef || p.symbols[superCtorRef.InnerIndex].UseCountEstimate == 0 {
|
|
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...
|
|
// return this;
|
|
// };
|
|
//
|
|
argsRef := p.newSymbol(ast.SymbolOther, "args")
|
|
p.currentScope.Generated = append(p.currentScope.Generated, argsRef)
|
|
p.recordUsage(argsRef)
|
|
superCall := 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 = append(append(
|
|
[]js_ast.Stmt{{Loc: body.Loc, Data: &js_ast.SExpr{Value: superCall}}},
|
|
stmtsToInsert...),
|
|
js_ast.Stmt{Loc: body.Loc, Data: &js_ast.SReturn{ValueOrNil: js_ast.Expr{Loc: body.Loc, Data: js_ast.EThisShared}}},
|
|
)
|
|
if p.options.minifySyntax {
|
|
stmtsToInsert = p.mangleStmts(stmtsToInsert, stmtsFnBody)
|
|
}
|
|
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,
|
|
PreferExpr: 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 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{}
|
|
}
|