// Polyfill this in case it's missing if (!('metadata' in Symbol as any)) { (Symbol as any).metadata = Symbol('Symbol.metadata') } if (!(Symbol.metadata in Function)) { Object.defineProperty((Function as any).prototype, Symbol.metadata, { value: null }) } const tests: Record Promise | void> = { // Class decorators 'Class decorators: Basic statement': () => { let old: { new(): Foo } const dec = (name: string) => (cls: { new(): Foo }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } @dec('Foo') class Foo { } assertEq(() => Foo, old!) }, 'Class decorators: Basic expression: Anonymous': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } const Foo = (x => x)(@dec('') class { }) assertEq(() => Foo, old!) const Bar = (x => x)(@dec('Baz') class Baz { }) assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Property value': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } const obj = { Foo: @dec('Foo') class { }, } assertEq(() => obj.Foo, old!) const obj2 = { Bar: @dec('Baz') class Baz { }, } assertEq(() => obj2.Bar, old!) }, 'Class decorators: Basic expression: Variable initializer': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } const Foo = @dec('Foo') class { } assertEq(() => Foo, old!) const Bar = @dec('Baz') class Baz { } assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Array binding': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } const [Foo = @dec('Foo') class { }] = [] assertEq(() => Foo, old!) const [Bar = @dec('Baz') class Baz { }] = [] assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Object binding': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } const { Foo = @dec('Foo') class { } } = {} assertEq(() => Foo, old!) const { Bar = @dec('Baz') class Baz { } } = {} assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Assignment initializer': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } let Foo: { new(): unknown } Foo = @dec('Foo') class { } assertEq(() => Foo, old!) let Bar: { new(): unknown } Bar = @dec('Baz') class Baz { } assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Assignment array binding': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } let Foo: { new(): unknown }; [Foo = @dec('Foo') class { }] = [] assertEq(() => Foo, old!) let Bar: { new(): unknown }; [Bar = @dec('Baz') class Baz { }] = [] assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Assignment object binding': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } let Foo: { new(): unknown }; ({ Foo = @dec('Foo') class { } } = {}) assertEq(() => Foo, old!) let Bar: { new(): unknown }; ({ Bar = @dec('Baz') class Baz { } } = {}) assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Instance field initializer': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } class Class { Foo = @dec('Foo') class { } } const Foo = new Class().Foo assertEq(() => Foo, old!) class Class2 { Bar = @dec('Baz') class Baz { } } const Bar = new Class2().Bar assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Static field initializer': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } class Class { static Foo = @dec('Foo') class { } } assertEq(() => Class.Foo, old!) class Class2 { static Bar = @dec('Baz') class Baz { } } assertEq(() => Class2.Bar, old!) }, 'Class decorators: Basic expression: Instance auto-accessor initializer': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } class Class { accessor Foo = @dec('Foo') class { } } const Foo = new Class().Foo assertEq(() => Foo, old!) class Class2 { accessor Bar = @dec('Baz') class Baz { } } const Bar = new Class2().Bar assertEq(() => Bar, old!) }, 'Class decorators: Basic expression: Static auto-accessor initializer': () => { let old: { new(): unknown } const dec = (name: string) => (cls: { new(): unknown }, ctx: ClassDecoratorContext) => { assertEq(() => typeof cls, 'function') assertEq(() => cls.name, name) assertEq(() => ctx.kind, 'class') assertEq(() => ctx.name, name) assertEq(() => 'static' in ctx, false) assertEq(() => 'private' in ctx, false) assertEq(() => 'access' in ctx, false) old = cls } class Class { static accessor Foo = @dec('Foo') class { } } assertEq(() => Class.Foo, old!) class Class2 { static accessor Bar = @dec('Baz') class Baz { } } assertEq(() => Class2.Bar, old!) }, 'Class decorators: Order': () => { const log: number[] = [] let Bar: { new(): Foo } let Baz: { new(): Foo } const dec1 = (cls: { new(): Foo }, ctx: ClassDecoratorContext) => { log.push(2) Bar = function () { log.push(4) return new cls } as any return Bar } const dec2 = (cls: { new(): Foo }, ctx: ClassDecoratorContext) => { log.push(1) Baz = function () { log.push(5) return new cls } as any return Baz } log.push(0) @dec1 @dec2 class Foo { constructor() { log.push(6) } } log.push(3) new Foo log.push(7) assertEq(() => Foo, Bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Class decorators: Return null': () => { assertThrows(() => { const dec = (cls: { new(): Foo }, ctx: ClassDecoratorContext): any => { return null } @dec class Foo { } }, TypeError) }, 'Class decorators: Return object': () => { assertThrows(() => { const dec = (cls: { new(): Foo }, ctx: ClassDecoratorContext): any => { return {} } @dec class Foo { } }, TypeError) }, 'Class decorators: Extra initializer': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (cls: { new(): Foo }, ctx: ClassDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } @dec @dec class Foo { } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, // Method decorators 'Method decorators: Basic (instance method)': () => { const old: Record void> = {} const dec = (key: PropertyKey, name: string) => (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, name) assertEq(() => ctx.kind, 'method') assertEq(() => ctx.name, key) assertEq(() => ctx.static, false) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => (ctx.access.get as any)({ [key]: 123 }), 123) assertEq(() => 'set' in ctx.access, false) old[key] = fn } const bar = Symbol('bar') const baz = Symbol() class Foo { @dec('foo', 'foo') foo() { } @dec(bar, '[bar]') [bar]() { } @dec(baz, '') [baz]() { } } assertEq(() => Foo.prototype.foo, old['foo']) assertEq(() => Foo.prototype[bar], old[bar]) assertEq(() => Foo.prototype[baz], old[baz]) }, 'Method decorators: Basic (static method)': () => { const old: Record void> = {} const dec = (key: PropertyKey, name: string) => (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, name) assertEq(() => ctx.kind, 'method') assertEq(() => ctx.name, key) assertEq(() => ctx.static, true) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => (ctx.access.get as any)({ [key]: 123 }), 123) assertEq(() => 'set' in ctx.access, false) old[key] = fn } const bar = Symbol('bar') const baz = Symbol() class Foo { @dec('foo', 'foo') static foo() { } @dec(bar, '[bar]') static [bar]() { } @dec(baz, '') static [baz]() { } } assertEq(() => Foo.foo, old['foo']) assertEq(() => Foo[bar], old[bar]) assertEq(() => Foo[baz], old[baz]) }, 'Method decorators: Basic (private instance method)': () => { let old: (this: Foo) => void let lateAsserts: () => void const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, '#foo') assertEq(() => ctx.kind, 'method') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, false) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(new Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(new Foo), $foo) assertEq(() => 'set' in ctx.access, false) } old = fn } let $foo: Function class Foo { @dec #foo() { } static { $foo = new Foo().#foo } } assertEq(() => $foo, old!) lateAsserts!() }, 'Method decorators: Basic (private static method)': () => { let old: (this: typeof Foo) => void let lateAsserts: () => void const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, '#foo') assertEq(() => ctx.kind, 'method') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, true) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(Foo), $foo) assertEq(() => 'set' in ctx.access, false) } old = fn } let $foo: Function class Foo { @dec static #foo() { } static { $foo = this.#foo } } assertEq(() => $foo, old!) lateAsserts!() }, 'Method decorators: Shim (instance method)': () => { let bar: (this: Foo) => number const dec = (fn: (this: Foo) => number, ctx: ClassMethodDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } class Foo { bar = 123 @dec foo() { return this.bar } } assertEq(() => Foo.prototype.foo, bar!) assertEq(() => new Foo().foo(), 124) }, 'Method decorators: Shim (static method)': () => { let bar: (this: typeof Foo) => number const dec = (fn: (this: typeof Foo) => number, ctx: ClassMethodDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } class Foo { static bar = 123 @dec static foo() { return this.bar } } assertEq(() => Foo.foo, bar!) assertEq(() => Foo.foo(), 124) }, 'Method decorators: Shim (private instance method)': () => { let bar: (this: Foo) => number const dec = (fn: (this: Foo) => number, ctx: ClassMethodDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } let $foo: (this: Foo) => number class Foo { bar = 123 @dec #foo() { return this.bar } static { $foo = new Foo().#foo } } assertEq(() => $foo, bar!) assertEq(() => bar.call(new Foo), 124) }, 'Method decorators: Shim (private static method)': () => { let bar: (this: typeof Foo) => number const dec = (fn: (this: typeof Foo) => number, ctx: ClassMethodDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } let $foo: (this: Foo) => number class Foo { static bar = 123 @dec static #foo() { return this.bar } static { $foo = this.#foo } } assertEq(() => $foo, bar!) assertEq(() => bar.call(Foo), 124) }, 'Method decorators: Order (instance method)': () => { const log: number[] = [] let bar: (this: Foo) => number let baz: (this: Foo) => number const dec1 = (fn: (this: Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) class Foo { @dec1 @dec2 foo() { return log.push(6) } } log.push(3) new Foo().foo() log.push(7) assertEq(() => Foo.prototype.foo, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Method decorators: Order (static method)': () => { const log: number[] = [] let bar: (this: typeof Foo) => number let baz: (this: typeof Foo) => number const dec1 = (fn: (this: typeof Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: typeof Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) class Foo { @dec1 @dec2 static foo() { return log.push(6) } } log.push(3) Foo.foo() log.push(7) assertEq(() => Foo.foo, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Method decorators: Order (private instance method)': () => { const log: number[] = [] let bar: (this: Foo) => number let baz: (this: Foo) => number const dec1 = (fn: (this: Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) let $foo: Function class Foo { @dec1 @dec2 #foo() { return log.push(6) } static { $foo = new Foo().#foo } } log.push(3) $foo.call(new Foo) log.push(7) assertEq(() => $foo, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Method decorators: Order (private static method)': () => { const log: number[] = [] let bar: (this: typeof Foo) => number let baz: (this: typeof Foo) => number const dec1 = (fn: (this: typeof Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: typeof Foo) => number, ctx: ClassMethodDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) let $foo: (this: Foo) => number class Foo { @dec1 @dec2 static #foo() { return log.push(6) } static { $foo = Foo.#foo } } log.push(3) $foo.call(Foo) log.push(7) assertEq(() => $foo, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Method decorators: Return null (instance method)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext): any => { return null } class Foo { @dec foo() { } } }, TypeError) }, 'Method decorators: Return null (static method)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext): any => { return null } class Foo { @dec static foo() { } } }, TypeError) }, 'Method decorators: Return null (private instance method)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext): any => { return null } class Foo { @dec #foo() { } } }, TypeError) }, 'Method decorators: Return null (private static method)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext): any => { return null } class Foo { @dec static #foo() { } } }, TypeError) }, 'Method decorators: Return object (instance method)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext): any => { return {} } class Foo { @dec foo() { } } }, TypeError) }, 'Method decorators: Return object (static method)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext): any => { return {} } class Foo { @dec static foo() { } } }, TypeError) }, 'Method decorators: Return object (private instance method)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext): any => { return {} } class Foo { @dec #foo() { } } }, TypeError) }, 'Method decorators: Return object (private static method)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext): any => { return {} } class Foo { @dec static #foo() { } } }, TypeError) }, 'Method decorators: Extra initializer (instance method)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec foo() { } } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Method decorators: Extra initializer (static method)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static foo() { } } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, 'Method decorators: Extra initializer (private instance method)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: Foo) => void, ctx: ClassMethodDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec #foo() { } } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Method decorators: Extra initializer (private static method)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: typeof Foo) => void, ctx: ClassMethodDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static #foo() { } } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, // Field decorators 'Field decorators: Basic (instance field)': () => { const dec = (key: PropertyKey) => (value: undefined, ctx: ClassFieldDecoratorContext) => { assertEq(() => value, undefined) assertEq(() => ctx.kind, 'field') assertEq(() => ctx.name, key) assertEq(() => ctx.static, false) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => ctx.access.get({ [key]: 123 }), 123) assertEq(() => { const obj: any = {} ctx.access.set(obj, 321) return obj[key] }, 321) } const bar = Symbol('bar') const baz = Symbol() class Foo { @dec('foo') foo = 123 @dec(bar) [bar] = 123 @dec(baz) [baz] = 123 } assertEq(() => new Foo().foo, 123) assertEq(() => new Foo()[bar], 123) assertEq(() => new Foo()[baz], 123) }, 'Field decorators: Basic (static field)': () => { const dec = (key: PropertyKey) => (value: undefined, ctx: ClassFieldDecoratorContext) => { assertEq(() => value, undefined) assertEq(() => ctx.kind, 'field') assertEq(() => ctx.name, key) assertEq(() => ctx.static, true) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => ctx.access.get({ [key]: 123 }), 123) assertEq(() => { const obj: any = {} ctx.access.set(obj, 321) return obj[key] }, 321) } const bar = Symbol('bar') const baz = Symbol() class Foo { @dec('foo') static foo = 123 @dec(bar) static [bar] = 123 @dec(baz) static [baz] = 123 } assertEq(() => Foo.foo, 123) assertEq(() => Foo[bar], 123) assertEq(() => Foo[baz], 123) }, 'Field decorators: Basic (private instance field)': () => { let lateAsserts: () => void const dec = (value: undefined, ctx: ClassFieldDecoratorContext) => { assertEq(() => value, undefined) assertEq(() => ctx.kind, 'field') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, false) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(new Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(new Foo), 123) assertEq(() => { const obj = new Foo ctx.access.set(obj, 321) return get$foo(obj) }, 321) } } let get$foo: (x: Foo) => number class Foo { @dec #foo = 123 static { get$foo = x => x.#foo } } assertEq(() => get$foo(new Foo()), 123) lateAsserts!() }, 'Field decorators: Basic (private static field)': () => { let lateAsserts: () => void const dec = (value: undefined, ctx: ClassFieldDecoratorContext) => { assertEq(() => value, undefined) assertEq(() => ctx.kind, 'field') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, true) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(Foo), 123) assertEq(() => { ctx.access.set(Foo, 321) return get$foo(Foo) }, 321) } } let get$foo: (x: typeof Foo) => number class Foo { @dec static #foo = 123 static { get$foo = x => x.#foo } } assertEq(() => get$foo(Foo), 123) lateAsserts!() }, 'Field decorators: Shim (instance field)': () => { let log: (boolean | number)[] = [] const dec = (value: undefined, ctx: ClassFieldDecoratorContext) => { return function (this: Foo, x: number) { assertEq(() => this instanceof Foo, true) return log.push('foo' in this, 'bar' in this, x) } } class Foo { @dec foo = 123 @dec bar!: number } assertEq(() => log + '', '') var obj = new Foo assertEq(() => obj.foo, 3) assertEq(() => obj.bar, 6) assertEq(() => log + '', 'false,false,123,true,false,') }, 'Field decorators: Shim (static field)': () => { let foo: typeof Foo let log: (boolean | number)[] = [] const dec = (value: undefined, ctx: ClassFieldDecoratorContext) => { return function (this: typeof Foo, x: number) { assertEq(() => this, foo) return log.push('foo' in this, 'bar' in this, x) } } assertEq(() => log + '', '') class Foo { static { foo = Foo } @dec static foo = 123 @dec static bar: number } assertEq(() => Foo.foo, 3) assertEq(() => Foo.bar, 6) assertEq(() => log + '', 'false,false,123,true,false,') }, 'Field decorators: Shim (private instance field)': () => { let log: (boolean | number)[] = [] const dec = (value: undefined, ctx: ClassFieldDecoratorContext) => { return function (this: Foo, x: number) { assertEq(() => this instanceof Foo, true) return log.push(has$foo(this), has$bar(this), x) } } let has$foo: (x: Foo) => boolean let has$bar: (x: Foo) => boolean let get$foo: (x: Foo) => number let get$bar: (x: Foo) => number class Foo { @dec #foo = 123 @dec #bar!: number static { has$foo = x => #foo in x has$bar = x => #bar in x get$foo = x => x.#foo get$bar = x => x.#bar } } assertEq(() => log + '', '') var obj = new Foo assertEq(() => get$foo(obj), 3) assertEq(() => get$bar(obj), 6) assertEq(() => log + '', 'false,false,123,true,false,') }, 'Field decorators: Shim (private static field)': () => { let foo: typeof Foo let log: (boolean | number)[] = [] const dec = (value: undefined, ctx: ClassFieldDecoratorContext) => { return function (this: typeof Foo, x: number) { assertEq(() => this, foo) return log.push(has$foo(this), has$bar(this), x) } } assertEq(() => log + '', '') let has$foo: (x: typeof Foo) => boolean let has$bar: (x: typeof Foo) => boolean let get$foo: (x: typeof Foo) => number let get$bar: (x: typeof Foo) => number class Foo { static { foo = Foo has$foo = x => #foo in x has$bar = x => #bar in x get$foo = x => x.#foo get$bar = x => x.#bar } @dec static #foo = 123 @dec static #bar: number } assertEq(() => get$foo(Foo), 3) assertEq(() => get$bar(Foo), 6) assertEq(() => log + '', 'false,false,123,true,false,') }, 'Field decorators: Order (instance field)': () => { const log: number[] = [] const dec1 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(2) return () => log.push(4) } const dec2 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(1) return () => log.push(5) } log.push(0) class Foo { @dec1 @dec2 foo = 123 } log.push(3) var obj = new Foo() log.push(6) assertEq(() => obj.foo, 6) assertEq(() => log + '', '0,1,2,3,4,5,6') }, 'Field decorators: Order (static field)': () => { const log: number[] = [] const dec1 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(2) return () => log.push(3) } const dec2 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(1) return () => log.push(4) } log.push(0) class Foo { @dec1 @dec2 static foo = 123 } log.push(5) assertEq(() => Foo.foo, 5) assertEq(() => log + '', '0,1,2,3,4,5') }, 'Field decorators: Order (private instance field)': () => { const log: number[] = [] const dec1 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(2) return () => log.push(4) } const dec2 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(1) return () => log.push(5) } log.push(0) let get$foo: (x: Foo) => number class Foo { @dec1 @dec2 #foo = 123 static { get$foo = x => x.#foo } } log.push(3) var obj = new Foo() log.push(6) assertEq(() => get$foo(obj), 6) assertEq(() => log + '', '0,1,2,3,4,5,6') }, 'Field decorators: Order (private static field)': () => { const log: number[] = [] const dec1 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(2) return () => log.push(3) } const dec2 = (value: undefined, ctx: ClassFieldDecoratorContext) => { log.push(1) return () => log.push(4) } log.push(0) let get$foo: (x: typeof Foo) => number class Foo { @dec1 @dec2 static #foo = 123 static { get$foo = x => x.#foo } } log.push(5) assertEq(() => get$foo(Foo), 5) assertEq(() => log + '', '0,1,2,3,4,5') }, 'Field decorators: Return null (instance field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return null } class Foo { @dec foo: undefined } }, TypeError) }, 'Field decorators: Return null (static field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return null } class Foo { @dec static foo: undefined } }, TypeError) }, 'Field decorators: Return null (private instance field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return null } class Foo { @dec #foo: undefined } }, TypeError) }, 'Field decorators: Return null (private static field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return null } class Foo { @dec static #foo: undefined } }, TypeError) }, 'Field decorators: Return object (instance field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return {} } class Foo { @dec foo: undefined } }, TypeError) }, 'Field decorators: Return object (static field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return {} } class Foo { @dec static foo: undefined } }, TypeError) }, 'Field decorators: Return object (private instance field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return {} } class Foo { @dec #foo: undefined } }, TypeError) }, 'Field decorators: Return object (private static field)': () => { assertThrows(() => { const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { return {} } class Foo { @dec static #foo: undefined } }, TypeError) }, 'Field decorators: Extra initializer (instance field)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec foo: undefined } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Field decorators: Extra initializer (static field)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static foo: undefined } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, 'Field decorators: Extra initializer (private instance field)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec #foo: undefined } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Field decorators: Extra initializer (private static field)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (value: undefined, ctx: ClassFieldDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static #foo: undefined } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, // Getter decorators 'Getter decorators: Basic (instance getter)': () => { const dec = (key: PropertyKey, name: string) => (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, name) assertEq(() => ctx.kind, 'getter') assertEq(() => ctx.name, key) assertEq(() => ctx.static, false) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => ctx.access.get({ [key]: 123 }), 123) assertEq(() => 'set' in ctx.access, false) } const bar = Symbol('bar') const baz = Symbol() class Foo { bar = 123 @dec('foo', 'get foo') get foo() { return this.bar } @dec(bar, 'get [bar]') get [bar]() { return this.bar } @dec(baz, 'get ') get [baz]() { return this.bar } } assertEq(() => new Foo().foo, 123) assertEq(() => new Foo()[bar], 123) assertEq(() => new Foo()[baz], 123) }, 'Getter decorators: Basic (static getter)': () => { const dec = (key: PropertyKey, name: string) => (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, name) assertEq(() => ctx.kind, 'getter') assertEq(() => ctx.name, key) assertEq(() => ctx.static, true) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => ctx.access.get({ [key]: 123 }), 123) assertEq(() => 'set' in ctx.access, false) } const bar = Symbol('bar') const baz = Symbol() class Foo { static bar = 123 @dec('foo', 'get foo') static get foo() { return this.bar } @dec(bar, 'get [bar]') static get [bar]() { return this.bar } @dec(baz, 'get ') static get [baz]() { return this.bar } } assertEq(() => Foo.foo, 123) assertEq(() => Foo[bar], 123) assertEq(() => Foo[baz], 123) }, 'Getter decorators: Basic (private instance getter)': () => { let lateAsserts: () => void const dec = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, 'get #foo') assertEq(() => ctx.kind, 'getter') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, false) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(new Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(new Foo), 123) assertEq(() => 'set' in ctx.access, false) } } let get$foo: (x: Foo) => number class Foo { #bar = 123 @dec get #foo() { return this.#bar } static { get$foo = x => x.#foo } } assertEq(() => get$foo(new Foo), 123) lateAsserts!() }, 'Getter decorators: Basic (private static getter)': () => { let lateAsserts: () => void const dec = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, 'get #foo') assertEq(() => ctx.kind, 'getter') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, true) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(Foo), 123) assertEq(() => 'set' in ctx.access, false) } } let get$foo: (x: typeof Foo) => number class Foo { static #bar = 123 @dec static get #foo() { return this.#bar } static { get$foo = x => x.#foo } } assertEq(() => get$foo(Foo), 123) lateAsserts!() }, 'Getter decorators: Shim (instance getter)': () => { let bar: (this: Foo) => number const dec = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } class Foo { bar = 123 @dec get foo() { return this.bar } } assertEq(() => Object.getOwnPropertyDescriptor(Foo.prototype, 'foo')!.get, bar!) assertEq(() => new Foo().foo, 124) }, 'Getter decorators: Shim (static getter)': () => { let bar: (this: typeof Foo) => number const dec = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } class Foo { static bar = 123 @dec static get foo() { return this.bar } } assertEq(() => Object.getOwnPropertyDescriptor(Foo, 'foo')!.get, bar!) assertEq(() => Foo.foo, 124) }, 'Getter decorators: Shim (private instance getter)': () => { let bar: (this: Foo) => number const dec = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } let get$foo: (x: Foo) => number class Foo { #bar = 123 @dec get #foo() { return this.#bar } static { get$foo = x => x.#foo } } assertEq(() => get$foo(new Foo), 124) }, 'Getter decorators: Shim (private static getter)': () => { let bar: (this: typeof Foo) => number const dec = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { bar = function () { return fn.call(this) + 1 } return bar } let get$foo: (x: typeof Foo) => number class Foo { static #bar = 123 @dec static get #foo() { return this.#bar } static { get$foo = x => x.#foo } } assertEq(() => get$foo(Foo), 124) }, 'Getter decorators: Order (instance getter)': () => { const log: number[] = [] let bar: (this: Foo) => number let baz: (this: Foo) => number const dec1 = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) class Foo { @dec1 @dec2 get foo() { return log.push(6) } } log.push(3) new Foo().foo log.push(7) assertEq(() => Object.getOwnPropertyDescriptor(Foo.prototype, 'foo')!.get, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Getter decorators: Order (static getter)': () => { const log: number[] = [] let bar: (this: typeof Foo) => number let baz: (this: typeof Foo) => number const dec1 = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) class Foo { @dec1 @dec2 static get foo() { return log.push(6) } } log.push(3) Foo.foo log.push(7) assertEq(() => Object.getOwnPropertyDescriptor(Foo, 'foo')!.get, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Getter decorators: Order (private instance getter)': () => { const log: number[] = [] let bar: (this: Foo) => number let baz: (this: Foo) => number const dec1 = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) let get$foo: (x: Foo) => number class Foo { @dec1 @dec2 get #foo() { return log.push(6) } static { get$foo = x => x.#foo } } log.push(3) assertEq(() => get$foo(new Foo), 7) log.push(7) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Getter decorators: Order (private static getter)': () => { const log: number[] = [] let bar: (this: typeof Foo) => number let baz: (this: typeof Foo) => number const dec1 = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(2) bar = function () { log.push(4) return fn.call(this) } return bar } const dec2 = (fn: (this: typeof Foo) => number, ctx: ClassGetterDecoratorContext) => { log.push(1) baz = function () { log.push(5) return fn.call(this) } return baz } log.push(0) let get$foo: (x: typeof Foo) => number class Foo { @dec1 @dec2 static get #foo() { return log.push(6) } static { get$foo = x => x.#foo } } log.push(3) assertEq(() => get$foo(Foo), 7) log.push(7) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Getter decorators: Return null (instance getter)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return null } class Foo { @dec get foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return null (static getter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return null } class Foo { @dec static get foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return null (private instance getter)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return null } class Foo { @dec get #foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return null (private static getter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return null } class Foo { @dec static get #foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return object (instance getter)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return {} } class Foo { @dec get foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return object (static getter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return {} } class Foo { @dec static get foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return object (private instance getter)': () => { assertThrows(() => { const dec = (fn: (this: Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return {} } class Foo { @dec get #foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Return object (private static getter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { return {} } class Foo { @dec static get #foo(): undefined { return } } }, TypeError) }, 'Getter decorators: Extra initializer (instance getter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec get foo(): undefined { return } } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Getter decorators: Extra initializer (static getter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: typeof Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static get foo(): undefined { return } } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, 'Getter decorators: Extra initializer (private instance getter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec get #foo(): undefined { return } } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Getter decorators: Extra initializer (private static getter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: typeof Foo) => undefined, ctx: ClassGetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static get #foo(): undefined { return } } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, // Setter decorators 'Setter decorators: Basic (instance setter)': () => { const dec = (key: PropertyKey, name: string) => (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, name) assertEq(() => ctx.kind, 'setter') assertEq(() => ctx.name, key) assertEq(() => ctx.static, false) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => 'get' in ctx.access, false) const obj: any = {} ctx.access.set(obj, 123) assertEq(() => obj[key], 123) assertEq(() => 'bar' in obj, false) } const bar = Symbol('bar') const baz = Symbol() class Foo { bar = 0 @dec('foo', 'set foo') set foo(x: number) { this.bar = x } @dec(bar, 'set [bar]') set [bar](x: number) { this.bar = x } @dec(baz, 'set ') set [baz](x: number) { this.bar = x } } var obj = new Foo obj.foo = 321 assertEq(() => obj.bar, 321) obj[bar] = 4321 assertEq(() => obj.bar, 4321) obj[baz] = 54321 assertEq(() => obj.bar, 54321) }, 'Setter decorators: Basic (static setter)': () => { const dec = (key: PropertyKey, name: string) => (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, name) assertEq(() => ctx.kind, 'setter') assertEq(() => ctx.name, key) assertEq(() => ctx.static, true) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => 'get' in ctx.access, false) const obj: any = {} ctx.access.set(obj, 123) assertEq(() => obj[key], 123) assertEq(() => 'bar' in obj, false) } const bar = Symbol('bar') const baz = Symbol() class Foo { static bar = 0 @dec('foo', 'set foo') static set foo(x: number) { this.bar = x } @dec(bar, 'set [bar]') static set [bar](x: number) { this.bar = x } @dec(baz, 'set ') static set [baz](x: number) { this.bar = x } } Foo.foo = 321 assertEq(() => Foo.bar, 321) Foo[bar] = 4321 assertEq(() => Foo.bar, 4321) Foo[baz] = 54321 assertEq(() => Foo.bar, 54321) }, 'Setter decorators: Basic (private instance setter)': () => { let lateAsserts: () => void const dec = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, 'set #foo') assertEq(() => ctx.kind, 'setter') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, false) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(new Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => 'get' in ctx.access, false) assertEq(() => { const obj = new Foo ctx.access.set(obj, 123) return obj.bar }, 123) } } let set$foo: (x: Foo, y: number) => void class Foo { bar = 0 @dec set #foo(x: number) { this.bar = x } static { set$foo = (x, y) => { x.#foo = y } } } lateAsserts!() var obj = new Foo assertEq(() => set$foo(obj, 321), undefined) assertEq(() => obj.bar, 321) }, 'Setter decorators: Basic (private static setter)': () => { let lateAsserts: () => void const dec = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { assertEq(() => typeof fn, 'function') assertEq(() => fn.name, 'set #foo') assertEq(() => ctx.kind, 'setter') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, true) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => 'get' in ctx.access, false) assertEq(() => { ctx.access.set(Foo, 123) return Foo.bar }, 123) } } let set$foo: (x: typeof Foo, y: number) => void class Foo { static bar = 0 @dec static set #foo(x: number) { this.bar = x } static { set$foo = (x, y) => { x.#foo = y } } } lateAsserts!() assertEq(() => set$foo(Foo, 321), undefined) assertEq(() => Foo.bar, 321) }, 'Setter decorators: Shim (instance setter)': () => { let bar: (this: Foo, x: number) => void const dec = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { bar = function (x) { fn.call(this, x + 1) } return bar } class Foo { bar = 123 @dec set foo(x: number) { this.bar = x } } assertEq(() => Object.getOwnPropertyDescriptor(Foo.prototype, 'foo')!.set, bar!) var obj = new Foo obj.foo = 321 assertEq(() => obj.bar, 322) }, 'Setter decorators: Shim (static setter)': () => { let bar: (this: typeof Foo, x: number) => void const dec = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { bar = function (x) { fn.call(this, x + 1) } return bar } class Foo { static bar = 123 @dec static set foo(x: number) { this.bar = x } } assertEq(() => Object.getOwnPropertyDescriptor(Foo, 'foo')!.set, bar!) Foo.foo = 321 assertEq(() => Foo.bar, 322) }, 'Setter decorators: Shim (private instance setter)': () => { let bar: (this: Foo, x: number) => void const dec = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { bar = function (x) { fn.call(this, x + 1) } return bar } let set$foo: (x: Foo, y: number) => void class Foo { bar = 123 @dec set #foo(x: number) { this.bar = x } static { set$foo = (x, y) => { x.#foo = y } } } var obj = new Foo assertEq(() => set$foo(obj, 321), undefined) assertEq(() => obj.bar, 322) }, 'Setter decorators: Shim (private static setter)': () => { let bar: (this: typeof Foo, x: number) => void const dec = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { bar = function (x) { fn.call(this, x + 1) } return bar } let set$foo: (x: typeof Foo, y: number) => void class Foo { static bar = 123 @dec static set #foo(x: number) { this.bar = x } static { set$foo = (x, y) => { x.#foo = y } } } assertEq(() => set$foo(Foo, 321), undefined) assertEq(() => Foo.bar, 322) }, 'Setter decorators: Order (instance setter)': () => { const log: number[] = [] let bar: (this: Foo, x: number) => void let baz: (this: Foo, x: number) => void const dec1 = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(2) bar = function (x) { log.push(4) fn.call(this, x) } return bar } const dec2 = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(1) baz = function (x) { log.push(5) fn.call(this, x) } return baz } log.push(0) class Foo { @dec1 @dec2 set foo(x: number) { log.push(6) } } log.push(3) new Foo().foo = 123 log.push(7) assertEq(() => Object.getOwnPropertyDescriptor(Foo.prototype, 'foo')!.set, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Setter decorators: Order (static setter)': () => { const log: number[] = [] let bar: (this: typeof Foo, x: number) => void let baz: (this: typeof Foo, x: number) => void const dec1 = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(2) bar = function (x) { log.push(4) fn.call(this, x) } return bar } const dec2 = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(1) baz = function (x) { log.push(5) fn.call(this, x) } return baz } log.push(0) class Foo { @dec1 @dec2 static set foo(x: number) { log.push(6) } } log.push(3) Foo.foo = 123 log.push(7) assertEq(() => Object.getOwnPropertyDescriptor(Foo, 'foo')!.set, bar!) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Setter decorators: Order (private instance setter)': () => { const log: number[] = [] let bar: (this: Foo, x: number) => void let baz: (this: Foo, x: number) => void const dec1 = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(2) bar = function (x) { log.push(4) fn.call(this, x) } return bar } const dec2 = (fn: (this: Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(1) baz = function (x) { log.push(5) fn.call(this, x) } return baz } log.push(0) let set$foo: (x: Foo, y: number) => void class Foo { @dec1 @dec2 set #foo(x: number) { log.push(6) } static { set$foo = (x, y) => { x.#foo = y } } } log.push(3) assertEq(() => set$foo(new Foo, 123), undefined) log.push(7) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Setter decorators: Order (private static setter)': () => { const log: number[] = [] let bar: (this: typeof Foo, x: number) => void let baz: (this: typeof Foo, x: number) => void const dec1 = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(2) bar = function (x) { log.push(4) fn.call(this, x) } return bar } const dec2 = (fn: (this: typeof Foo, x: number) => void, ctx: ClassSetterDecoratorContext) => { log.push(1) baz = function (x) { log.push(5) fn.call(this, x) } return baz } log.push(0) let set$foo: (x: typeof Foo, y: number) => void class Foo { @dec1 @dec2 static set #foo(x: number) { log.push(6) } static { set$foo = (x, y) => { x.#foo = y } } } log.push(3) assertEq(() => set$foo(Foo, 123), undefined) log.push(7) assertEq(() => log + '', '0,1,2,3,4,5,6,7') }, 'Setter decorators: Return null (instance setter)': () => { assertThrows(() => { const dec = (fn: (this: Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return null } class Foo { @dec set foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return null (static setter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return null } class Foo { @dec static set foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return null (private instance setter)': () => { assertThrows(() => { const dec = (fn: (this: Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return null } class Foo { @dec set #foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return null (private static setter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return null } class Foo { @dec static set #foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return object (instance setter)': () => { assertThrows(() => { const dec = (fn: (this: Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return {} } class Foo { @dec set foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return object (static setter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return {} } class Foo { @dec static set foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return object (private instance setter)': () => { assertThrows(() => { const dec = (fn: (this: Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return {} } class Foo { @dec set #foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Return object (private static setter)': () => { assertThrows(() => { const dec = (fn: (this: typeof Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { return {} } class Foo { @dec static set #foo(x: undefined) { } } }, TypeError) }, 'Setter decorators: Extra initializer (instance setter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec set foo(x: undefined) { } } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Setter decorators: Extra initializer (static setter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: typeof Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static set foo(x: undefined) { } } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, 'Setter decorators: Extra initializer (private instance setter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec set #foo(x: undefined) { } } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Setter decorators: Extra initializer (private static setter)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (fn: (this: typeof Foo, x: undefined) => void, ctx: ClassSetterDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static set #foo(x: undefined) { } } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, // Auto-accessor decorators 'Auto-accessor decorators: Basic (instance auto-accessor)': () => { const dec = (key: PropertyKey, getName: string, setName: string) => (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext) => { assertEq(() => typeof target.get, 'function') assertEq(() => typeof target.set, 'function') assertEq(() => target.get.name, getName) assertEq(() => target.set.name, setName) assertEq(() => ctx.kind, 'accessor') assertEq(() => ctx.name, key) assertEq(() => ctx.static, false) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => ctx.access.get({ [key]: 123 }), 123) assertEq(() => { const obj: any = {} ctx.access.set(obj, 123) return obj[key] }, 123) } const bar = Symbol('bar') const baz = Symbol() class Foo { @dec('foo', 'get foo', 'set foo') accessor foo = 0 @dec(bar, 'get [bar]', 'set [bar]') accessor [bar] = 0 @dec(baz, 'get ', 'set ') accessor [baz] = 0 } var obj = new Foo obj.foo = 321 assertEq(() => obj.foo, 321) obj[bar] = 4321 assertEq(() => obj[bar], 4321) obj[baz] = 54321 assertEq(() => obj[baz], 54321) }, 'Auto-accessor decorators: Basic (static auto-accessor)': () => { const dec = (key: PropertyKey, getName: string, setName: string) => (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext) => { assertEq(() => typeof target.get, 'function') assertEq(() => typeof target.set, 'function') assertEq(() => target.get.name, getName) assertEq(() => target.set.name, setName) assertEq(() => ctx.kind, 'accessor') assertEq(() => ctx.name, key) assertEq(() => ctx.static, true) assertEq(() => ctx.private, false) assertEq(() => ctx.access.has({ [key]: false }), true) assertEq(() => ctx.access.has({ bar: true }), false) assertEq(() => ctx.access.get({ [key]: 123 }), 123) assertEq(() => { const obj: any = {} ctx.access.set(obj, 123) return obj[key] }, 123) } const bar = Symbol('bar') const baz = Symbol() class Foo { @dec('foo', 'get foo', 'set foo') static accessor foo = 0 @dec(bar, 'get [bar]', 'set [bar]') static accessor [bar] = 0 @dec(baz, 'get ', 'set ') static accessor [baz] = 0 } Foo.foo = 321 assertEq(() => Foo.foo, 321) Foo[bar] = 4321 assertEq(() => Foo[bar], 4321) Foo[baz] = 54321 assertEq(() => Foo[baz], 54321) }, 'Auto-accessor decorators: Basic (private instance auto-accessor)': () => { let lateAsserts: () => void const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext) => { assertEq(() => typeof target.get, 'function') assertEq(() => typeof target.set, 'function') assertEq(() => target.get.name, 'get #foo') assertEq(() => target.set.name, 'set #foo') assertEq(() => ctx.kind, 'accessor') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, false) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(new Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(new Foo), 0) assertEq(() => { const obj = new Foo ctx.access.set(obj, 123) return get$foo(obj) }, 123) } } let get$foo: (x: Foo) => number let set$foo: (x: Foo, y: number) => void class Foo { @dec accessor #foo = 0 static { get$foo = x => x.#foo set$foo = (x, y) => { x.#foo = y } } } lateAsserts!() var obj = new Foo assertEq(() => set$foo(obj, 321), undefined) assertEq(() => get$foo(obj), 321) }, 'Auto-accessor decorators: Basic (private static auto-accessor)': () => { let lateAsserts: () => void const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext) => { assertEq(() => typeof target.get, 'function') assertEq(() => typeof target.set, 'function') assertEq(() => target.get.name, 'get #foo') assertEq(() => target.set.name, 'set #foo') assertEq(() => ctx.kind, 'accessor') assertEq(() => ctx.name, '#foo') assertEq(() => ctx.static, true) assertEq(() => ctx.private, true) lateAsserts = () => { assertEq(() => ctx.access.has(Foo), true) assertEq(() => ctx.access.has({}), false) assertEq(() => ctx.access.get(Foo), 0) assertEq(() => { ctx.access.set(Foo, 123) return get$foo(Foo) }, 123) } } let get$foo: (x: typeof Foo) => number let set$foo: (x: typeof Foo, y: number) => void class Foo { @dec static accessor #foo = 0 static { get$foo = x => x.#foo set$foo = (x, y) => { x.#foo = y } } } lateAsserts!() assertEq(() => set$foo(Foo, 321), undefined) assertEq(() => get$foo(Foo), 321) }, 'Auto-accessor decorators: Shim (instance auto-accessor)': () => { let get: (this: Foo) => number let set: (this: Foo, x: number) => void const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult => { function init(this: Foo, x: number): number { assertEq(() => this instanceof Foo, true) return x + 1 } get = function () { return target.get.call(this) * 10 } set = function (x) { target.set.call(this, x * 2) } return { get, set, init } } class Foo { @dec accessor foo = 123 } assertEq(() => Object.getOwnPropertyDescriptor(Foo.prototype, 'foo')!.get, get!) assertEq(() => Object.getOwnPropertyDescriptor(Foo.prototype, 'foo')!.set, set!) var obj = new Foo assertEq(() => obj.foo, (123 + 1) * 10) obj.foo = 321 assertEq(() => obj.foo, (321 * 2) * 10) }, 'Auto-accessor decorators: Shim (static auto-accessor)': () => { let foo: typeof Foo let get: (this: typeof Foo) => number let set: (this: typeof Foo, x: number) => void const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult => { function init(this: typeof Foo, x: number): number { assertEq(() => this, foo) return x + 1 } get = function () { return target.get.call(this) * 10 } set = function (x) { target.set.call(this, x * 2) } return { get, set, init } } class Foo { static { foo = Foo } @dec static accessor foo = 123 } assertEq(() => Object.getOwnPropertyDescriptor(Foo, 'foo')!.get, get!) assertEq(() => Object.getOwnPropertyDescriptor(Foo, 'foo')!.set, set!) assertEq(() => Foo.foo, (123 + 1) * 10) Foo.foo = 321 assertEq(() => Foo.foo, (321 * 2) * 10) }, 'Auto-accessor decorators: Shim (private instance auto-accessor)': () => { let get: (this: Foo) => number let set: (this: Foo, x: number) => void const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult => { function init(this: Foo, x: number): number { assertEq(() => this instanceof Foo, true) return x + 1 } get = function () { return target.get.call(this) * 10 } set = function (x) { target.set.call(this, x * 2) } return { get, set, init } } let get$foo: (x: Foo) => number let set$foo: (x: Foo, y: number) => void class Foo { @dec accessor #foo = 123 static { get$foo = x => x.#foo set$foo = (x, y) => { x.#foo = y } } } var obj = new Foo assertEq(() => get$foo(obj), (123 + 1) * 10) assertEq(() => set$foo(obj, 321), undefined) assertEq(() => get$foo(obj), (321 * 2) * 10) }, 'Auto-accessor decorators: Shim (private static auto-accessor)': () => { let foo: typeof Foo let get: (this: typeof Foo) => number let set: (this: typeof Foo, x: number) => void const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult => { function init(this: typeof Foo, x: number): number { assertEq(() => this, foo) return x + 1 } get = function () { return target.get.call(this) * 10 } set = function (x) { target.set.call(this, x * 2) } return { get, set, init } } let get$foo: (x: typeof Foo) => number let set$foo: (x: typeof Foo, y: number) => void class Foo { static { foo = Foo get$foo = x => x.#foo set$foo = (x, y) => { x.#foo = y } } @dec static accessor #foo = 123 } assertEq(() => get$foo(Foo), (123 + 1) * 10) assertEq(() => set$foo(Foo, 321), undefined) assertEq(() => get$foo(Foo), (321 * 2) * 10) }, 'Auto-accessor decorators: Return null (instance auto-accessor)': () => { assertThrows(() => { const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { return null } class Foo { @dec accessor foo: undefined } }, TypeError) }, 'Auto-accessor decorators: Return null (static auto-accessor)': () => { assertThrows(() => { const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { return null } class Foo { @dec static accessor foo: undefined } }, TypeError) }, 'Auto-accessor decorators: Return null (private instance auto-accessor)': () => { assertThrows(() => { const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { return null } class Foo { @dec accessor #foo: undefined } }, TypeError) }, 'Auto-accessor decorators: Return null (private static auto-accessor)': () => { assertThrows(() => { const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { return null } class Foo { @dec static accessor #foo: undefined } }, TypeError) }, 'Auto-accessor decorators: Extra initializer (instance auto-accessor)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec accessor foo: undefined } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Auto-accessor decorators: Extra initializer (static auto-accessor)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static accessor foo: undefined } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, 'Auto-accessor decorators: Extra initializer (private instance auto-accessor)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec accessor #foo: undefined } assertEq(() => got, undefined) const instance = new Foo assertEq(() => got.this, instance) assertEq(() => got.args.length, 0) }, 'Auto-accessor decorators: Extra initializer (private static auto-accessor)': () => { let oldAddInitializer: DecoratorContext['addInitializer'] | null let got: { this: any, args: any[] } const dec = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext): any => { ctx.addInitializer(function (...args) { got = { this: this, args } }) if (oldAddInitializer) assertThrows(() => oldAddInitializer!(() => { }), TypeError) assertThrows(() => ctx.addInitializer({} as any), TypeError) oldAddInitializer = ctx.addInitializer } class Foo { @dec @dec static accessor #foo: undefined } assertEq(() => got.this, Foo) assertEq(() => got.args.length, 0) }, // Decorator list evaluation 'Decorator list evaluation: Computed names (class statement)': () => { const log: number[] = [] const foo = (n: number): Function => { log.push(n) return () => { } } const computed: { readonly method: unique symbol, readonly field: unique symbol, readonly getter: unique symbol, readonly setter: unique symbol, readonly accessor: unique symbol, } = { get method() { log.push(log.length); return Symbol('method') }, get field() { log.push(log.length); return Symbol('field') }, get getter() { log.push(log.length); return Symbol('getter') }, get setter() { log.push(log.length); return Symbol('setter') }, get accessor() { log.push(log.length); return Symbol('accessor') }, } as any @foo(0) class Foo extends (foo(1), Object) { @foo(2) [computed.method]() { } @foo(4) static [computed.method]() { } @foo(6) [computed.field]: undefined @foo(8) static [computed.field]: undefined @foo(10) get [computed.getter](): undefined { return } @foo(12) static get [computed.getter](): undefined { return } @foo(14) set [computed.setter](x: undefined) { } @foo(16) static set [computed.setter](x: undefined) { } @foo(18) accessor [computed.accessor]: undefined @foo(20) static accessor [computed.accessor]: undefined } assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21') }, 'Decorator list evaluation: Computed names (class expression)': () => { const log: number[] = [] const foo = (n: number): Function => { log.push(n) return () => { } } const computed: { readonly method: unique symbol, readonly field: unique symbol, readonly getter: unique symbol, readonly setter: unique symbol, readonly accessor: unique symbol, } = { get method() { log.push(log.length); return Symbol('method') }, get field() { log.push(log.length); return Symbol('field') }, get getter() { log.push(log.length); return Symbol('getter') }, get setter() { log.push(log.length); return Symbol('setter') }, get accessor() { log.push(log.length); return Symbol('accessor') }, } as any (@foo(0) class extends (foo(1), Object) { @foo(2) [computed.method]() { } @foo(4) static [computed.method]() { } @foo(6) [computed.field]: undefined @foo(8) static [computed.field]: undefined @foo(10) get [computed.getter](): undefined { return } @foo(12) static get [computed.getter](): undefined { return } @foo(14) set [computed.setter](x: undefined) { } @foo(16) static set [computed.setter](x: undefined) { } @foo(18) accessor [computed.accessor]: undefined @foo(20) static accessor [computed.accessor]: undefined }) assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21') }, 'Decorator list evaluation: "this" (class statement)': () => { const log: number[] = [] const dummy: Function = () => { } const ctx = { foo(n: number) { log.push(n) } } function wrapper(this: typeof ctx) { @(assertEq(() => this.foo(0), undefined), dummy) class Foo extends (assertEq(() => this.foo(1), undefined), Object) { @(assertEq(() => this.foo(2), undefined), dummy) method() { } @(assertEq(() => this.foo(3), undefined), dummy) static method() { } @(assertEq(() => this.foo(4), undefined), dummy) field: undefined @(assertEq(() => this.foo(5), undefined), dummy) static field: undefined @(assertEq(() => this.foo(6), undefined), dummy) get getter(): undefined { return } @(assertEq(() => this.foo(7), undefined), dummy) static get getter(): undefined { return } @(assertEq(() => this.foo(8), undefined), dummy) set setter(x: undefined) { } @(assertEq(() => this.foo(9), undefined), dummy) static set setter(x: undefined) { } @(assertEq(() => this.foo(10), undefined), dummy) accessor accessor: undefined @(assertEq(() => this.foo(11), undefined), dummy) static accessor accessor: undefined } } wrapper.call(ctx) assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11') }, 'Decorator list evaluation: "this" (class expression)': () => { const log: number[] = [] const dummy: Function = () => { } const ctx = { foo(n: number) { log.push(n) } } function wrapper(this: typeof ctx) { (@(assertEq(() => this.foo(0), undefined), dummy) class extends (assertEq(() => this.foo(1), undefined), Object) { @(assertEq(() => this.foo(2), undefined), dummy) method() { } @(assertEq(() => this.foo(3), undefined), dummy) static method() { } @(assertEq(() => this.foo(4), undefined), dummy) field: undefined @(assertEq(() => this.foo(5), undefined), dummy) static field: undefined @(assertEq(() => this.foo(6), undefined), dummy) get getter(): undefined { return } @(assertEq(() => this.foo(7), undefined), dummy) static get getter(): undefined { return } @(assertEq(() => this.foo(8), undefined), dummy) set setter(x: undefined) { } @(assertEq(() => this.foo(9), undefined), dummy) static set setter(x: undefined) { } @(assertEq(() => this.foo(10), undefined), dummy) accessor accessor: undefined @(assertEq(() => this.foo(11), undefined), dummy) static accessor accessor: undefined }) } wrapper.call(ctx) assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11') }, 'Decorator list evaluation: "await" (class statement)': async () => { const log: number[] = [] const dummy: Function = () => { } async function wrapper() { @(log.push(await Promise.resolve(0)), dummy) class Foo extends (log.push(await Promise.resolve(1)), Object) { @(log.push(await Promise.resolve(2)), dummy) method() { } @(log.push(await Promise.resolve(3)), dummy) static method() { } @(log.push(await Promise.resolve(4)), dummy) field: undefined @(log.push(await Promise.resolve(5)), dummy) static field: undefined @(log.push(await Promise.resolve(6)), dummy) get getter(): undefined { return } @(log.push(await Promise.resolve(7)), dummy) static get getter(): undefined { return } @(log.push(await Promise.resolve(8)), dummy) set setter(x: undefined) { } @(log.push(await Promise.resolve(9)), dummy) static set setter(x: undefined) { } @(log.push(await Promise.resolve(10)), dummy) accessor accessor: undefined @(log.push(await Promise.resolve(11)), dummy) static accessor accessor: undefined } } await wrapper() assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11') }, 'Decorator list evaluation: "await" (class expression)': async () => { const log: number[] = [] const dummy: Function = () => { } async function wrapper() { (@(log.push(await Promise.resolve(0)), dummy) class extends (log.push(await Promise.resolve(1)), Object) { @(log.push(await Promise.resolve(2)), dummy) method() { } @(log.push(await Promise.resolve(3)), dummy) static method() { } @(log.push(await Promise.resolve(4)), dummy) field: undefined @(log.push(await Promise.resolve(5)), dummy) static field: undefined @(log.push(await Promise.resolve(6)), dummy) get getter(): undefined { return } @(log.push(await Promise.resolve(7)), dummy) static get getter(): undefined { return } @(log.push(await Promise.resolve(8)), dummy) set setter(x: undefined) { } @(log.push(await Promise.resolve(9)), dummy) static set setter(x: undefined) { } @(log.push(await Promise.resolve(10)), dummy) accessor accessor: undefined @(log.push(await Promise.resolve(11)), dummy) static accessor accessor: undefined }) } await wrapper() assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11') }, 'Decorator list evaluation: Outer private name (class statement)': () => { const log: number[] = [] class Dummy { static #foo(n: number): Function { log.push(n) return () => { } } static { const dummy = this @(dummy.#foo(0)) class Foo extends (dummy.#foo(1), Object) { @(dummy.#foo(2)) method() { } @(dummy.#foo(3)) static method() { } @(dummy.#foo(4)) field: undefined @(dummy.#foo(5)) static field: undefined @(dummy.#foo(6)) get getter(): undefined { return } @(dummy.#foo(7)) static get getter(): undefined { return } @(dummy.#foo(8)) set setter(x: undefined) { } @(dummy.#foo(9)) static set setter(x: undefined) { } @(dummy.#foo(10)) accessor accessor: undefined @(dummy.#foo(11)) static accessor accessor: undefined } } } assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11') }, 'Decorator list evaluation: Outer private name (class expression)': () => { const log: number[] = [] class Dummy { static #foo(n: number): Function { log.push(n) return () => { } } static { const dummy = this; (@(dummy.#foo(0)) class extends (dummy.#foo(1), Object) { @(dummy.#foo(2)) method() { } @(dummy.#foo(3)) static method() { } @(dummy.#foo(4)) field: undefined @(dummy.#foo(5)) static field: undefined @(dummy.#foo(6)) get getter(): undefined { return } @(dummy.#foo(7)) static get getter(): undefined { return } @(dummy.#foo(8)) set setter(x: undefined) { } @(dummy.#foo(9)) static set setter(x: undefined) { } @(dummy.#foo(10)) accessor accessor: undefined @(dummy.#foo(11)) static accessor accessor: undefined }) } } assertEq(() => '' + log, '0,1,2,3,4,5,6,7,8,9,10,11') }, 'Decorator list evaluation: Inner private name (class statement)': () => { const fns: (() => number)[] = [] const capture = (fn: () => number): Function => { fns.push(fn) return () => { } } class Dummy { static #foo = NaN static { @(capture(() => (new Foo() as any).#foo + 0)) class Foo { #foo = 10 @(capture(() => new Foo().#foo + 1)) method() { } @(capture(() => new Foo().#foo + 2)) static method() { } @(capture(() => new Foo().#foo + 3)) field: undefined @(capture(() => new Foo().#foo + 4)) static field: undefined @(capture(() => new Foo().#foo + 5)) get getter(): undefined { return } @(capture(() => new Foo().#foo + 6)) static get getter(): undefined { return } @(capture(() => new Foo().#foo + 7)) set setter(x: undefined) { } @(capture(() => new Foo().#foo + 8)) static set setter(x: undefined) { } @(capture(() => new Foo().#foo + 9)) accessor accessor: undefined @(capture(() => new Foo().#foo + 10)) static accessor accessor: undefined } } } // Accessing "#foo" in the class decorator should fail. The "#foo" should // refer to the outer "#foo", not the inner "#foo". const firstFn = fns.shift()! assertEq(() => { try { firstFn() throw new Error('Expected a TypeError to be thrown') } catch (err) { if (err instanceof TypeError) return true throw err } }, true) // Accessing "#foo" from any of the class element decorators should succeed. // Each "#foo" should refer to the inner "#foo", not the outer "#foo". const log: number[] = [] for (const fn of fns) log.push(fn()) assertEq(() => '' + log, '11,12,13,14,15,16,17,18,19,20') }, 'Decorator list evaluation: Inner private name (class expression)': () => { const fns: (() => number)[] = [] const capture = (fn: () => number): Function => { fns.push(fn) return () => { } } class Outer { static #foo = 0 static { (@(capture(() => Outer.#foo + 0)) class Foo { #foo = 10 @(capture(() => new Foo().#foo + 1)) method() { } @(capture(() => new Foo().#foo + 2)) static method() { } @(capture(() => new Foo().#foo + 3)) field: undefined @(capture(() => new Foo().#foo + 4)) static field: undefined @(capture(() => new Foo().#foo + 5)) get getter(): undefined { return } @(capture(() => new Foo().#foo + 6)) static get getter(): undefined { return } @(capture(() => new Foo().#foo + 7)) set setter(x: undefined) { } @(capture(() => new Foo().#foo + 8)) static set setter(x: undefined) { } @(capture(() => new Foo().#foo + 9)) accessor accessor: undefined @(capture(() => new Foo().#foo + 10)) static accessor accessor: undefined }) } } // Accessing the outer "#foo" on "Outer" from within the class decorator // should succeed. Class decorators are evaluated in the outer private // environment before entering "ClassDefinitionEvaluation". // // Accessing the inner "#foo" on "Foo" from within any of the class element // decorators should also succeed. Class element decorators are evaluated // in the inner private environment inside "ClassDefinitionEvaluation". const log: number[] = [] for (const fn of fns) log.push(fn()) assertEq(() => '' + log, '0,11,12,13,14,15,16,17,18,19,20') }, 'Decorator list evaluation: Class binding (class statement)': () => { const fns: (() => typeof Foo)[] = [] const capture = (fn: () => typeof Foo): Function => { fns.push(fn) // Note: As far as I can tell, early reference to the class name should // throw a reference error because: // // 1. Class decorators run first in the top-level scope before entering // BindingClassDeclarationEvaluation. // // 2. Class element decorators run in ClassDefinitionEvaluation, which // runs ClassElementEvaluation for each class element before eventually // running classEnv.InitializeBinding(classBinding, F). // assertThrows(() => fn(), ReferenceError) return () => { } } @(capture(() => Foo)) class Foo { @(capture(() => Foo)) method() { } @(capture(() => Foo)) static method() { } @(capture(() => Foo)) field: undefined @(capture(() => Foo)) static field: undefined @(capture(() => Foo)) get getter(): undefined { return } @(capture(() => Foo)) static get getter(): undefined { return } @(capture(() => Foo)) set setter(x: undefined) { } @(capture(() => Foo)) static set setter(x: undefined) { } @(capture(() => Foo)) accessor accessor: undefined @(capture(() => Foo)) static accessor accessor: undefined } const originalFoo = Foo // Once we get here, these should all reference the now-initialized class, // either through classBinding (for class element decorators) or through // className (for decorators on the class itself). for (const fn of fns) { assertEq(() => fn(), originalFoo) } // Mutating a class binding is allowed in JavaScript. Let's test what // happens when we do this. (Foo as any) = null as any // As far as I can tell, class decorators should observe this change because // they are evaluated in the top-level scope. const firstFn = fns.shift()! assertEq(() => firstFn(), null) // But I believe class element decorators should not observe this change // because they are evaluated in the environment that exists for the // duration of ClassDefinitionEvaluation (i.e. classEnv, not env). for (const fn of fns) { assertEq(() => fn(), originalFoo) } }, 'Decorator list evaluation: Class binding (class expression)': () => { const fns: (() => { new(): Object })[] = [] const capture = (fn: () => { new(): Object }): Function => { fns.push(fn) return () => { } } const originalFoo = (@(capture(() => Foo)) class Foo { @(capture(() => Foo)) method() { } @(capture(() => Foo)) static method() { } @(capture(() => Foo)) field: undefined @(capture(() => Foo)) static field: undefined @(capture(() => Foo)) get getter(): undefined { return } @(capture(() => Foo)) static get getter(): undefined { return } @(capture(() => Foo)) set setter(x: undefined) { } @(capture(() => Foo)) static set setter(x: undefined) { } @(capture(() => Foo)) accessor accessor: undefined @(capture(() => Foo)) static accessor accessor: undefined }) // Decorators on the class itself should reference a global called "Foo", // which should still be a reference error. This is because a class // expression runs "DecoratorListEvaluation" in the outer environment and // then passes the evaluated decorators to "ClassDefinitionEvaluation". const firstFn = fns.shift()! assertThrows(() => firstFn(), ReferenceError) // All other decorators should reference the classBinding called "Foo", // which should now be initialized. This is because all other decorators // are evaluated within "ClassDefinitionEvaluation" while the running // execution context's environment is the nested class environment. for (const fn of fns) { assertEq(() => fn(), originalFoo) } }, // Decorator metadata 'Decorator metadata: class statement': () => { let counter = 0 const dec = (_: any, ctx: DecoratorContext) => { ctx.metadata[ctx.name!] = counter++ } @dec class Foo { @dec instanceField: undefined @dec accessor instanceAccessor: undefined @dec instanceMethod() { } @dec get instanceGetter() { return } @dec set instanceSetter(_: undefined) { } @dec static staticField: undefined @dec static accessor staticAccessor: undefined @dec static staticMethod() { } @dec static get staticGetter() { return } @dec static set staticSetter(_: undefined) { } } @dec class Bar extends Foo { @dec #instanceField: undefined @dec accessor #instanceAccessor: undefined @dec #instanceMethod() { } @dec get #instanceGetter() { return } @dec set #instanceSetter(_: undefined) { } @dec static #staticField: undefined @dec static accessor #staticAccessor: undefined @dec static #staticMethod() { } @dec static get #staticGetter() { return } @dec static set #staticSetter(_: undefined) { } } const order = (meta: DecoratorMetadataObject) => '' + [ meta['staticAccessor'], meta['staticMethod'], meta['staticGetter'], meta['staticSetter'], meta['#staticAccessor'], meta['#staticMethod'], meta['#staticGetter'], meta['#staticSetter'], meta['instanceAccessor'], meta['instanceMethod'], meta['instanceGetter'], meta['instanceSetter'], meta['#instanceAccessor'], meta['#instanceMethod'], meta['#instanceGetter'], meta['#instanceSetter'], meta['staticField'], meta['#staticField'], meta['instanceField'], meta['#instanceField'], meta['Foo'], meta['Bar'], ] const foo = Foo[Symbol.metadata]! const bar = Bar[Symbol.metadata]! assertEq(() => order(foo), '0,1,2,3,,,,,4,5,6,7,,,,,8,,9,,10,') assertEq(() => Object.getPrototypeOf(foo), null) assertEq(() => order(bar), '0,1,2,3,11,12,13,14,4,5,6,7,15,16,17,18,8,19,9,20,10,21') assertEq(() => Object.getPrototypeOf(bar), foo) // Test an undecorated class class FooNoDec { } class BarNoDec extends FooNoDec { } assertEq(() => FooNoDec[Symbol.metadata], null) assertEq(() => BarNoDec[Symbol.metadata], null) // Test a class with no class decorator class FooOneDec { @dec x: undefined } class BarOneDec extends FooOneDec { @dec y: undefined } assertEq(() => JSON.stringify(FooOneDec[Symbol.metadata]!), JSON.stringify({ x: 22 })) assertEq(() => JSON.stringify(BarOneDec[Symbol.metadata]!), JSON.stringify({ y: 23 })) assertEq(() => Object.getPrototypeOf(BarOneDec[Symbol.metadata]!), FooOneDec[Symbol.metadata]!) }, 'Decorator metadata: class expression': () => { let counter = 0 const dec = (_: any, ctx: DecoratorContext) => { ctx.metadata[ctx.name!] = counter++ } const Foo = @dec class { @dec instanceField: undefined @dec accessor instanceAccessor: undefined @dec instanceMethod() { } @dec get instanceGetter() { return } @dec set instanceSetter(_: undefined) { } @dec static staticField: undefined @dec static accessor staticAccessor: undefined @dec static staticMethod() { } @dec static get staticGetter() { return } @dec static set staticSetter(_: undefined) { } }, Bar = @dec class extends Foo { @dec #instanceField: undefined @dec accessor #instanceAccessor: undefined @dec #instanceMethod() { } @dec get #instanceGetter() { return } @dec set #instanceSetter(_: undefined) { } @dec static #staticField: undefined @dec static accessor #staticAccessor: undefined @dec static #staticMethod() { } @dec static get #staticGetter() { return } @dec static set #staticSetter(_: undefined) { } } const order = (meta: DecoratorMetadataObject) => '' + [ meta['staticAccessor'], meta['staticMethod'], meta['staticGetter'], meta['staticSetter'], meta['#staticAccessor'], meta['#staticMethod'], meta['#staticGetter'], meta['#staticSetter'], meta['instanceAccessor'], meta['instanceMethod'], meta['instanceGetter'], meta['instanceSetter'], meta['#instanceAccessor'], meta['#instanceMethod'], meta['#instanceGetter'], meta['#instanceSetter'], meta['staticField'], meta['#staticField'], meta['instanceField'], meta['#instanceField'], meta['Foo'], meta['Bar'], ] const foo = Foo[Symbol.metadata]! const bar = Bar[Symbol.metadata]! assertEq(() => order(foo), '0,1,2,3,,,,,4,5,6,7,,,,,8,,9,,10,') assertEq(() => Object.getPrototypeOf(foo), null) assertEq(() => order(bar), '0,1,2,3,11,12,13,14,4,5,6,7,15,16,17,18,8,19,9,20,10,21') assertEq(() => Object.getPrototypeOf(bar), foo) // Test an undecorated class const FooNoDec = class { } const BarNoDec = class extends FooNoDec { } assertEq(() => FooNoDec[Symbol.metadata], null) assertEq(() => BarNoDec[Symbol.metadata], null) // Test a class with no class decorator const FooOneDec = class { @dec x: undefined } const BarOneDec = class extends FooOneDec { @dec y: undefined } assertEq(() => JSON.stringify(FooOneDec[Symbol.metadata]!), JSON.stringify({ x: 22 })) assertEq(() => JSON.stringify(BarOneDec[Symbol.metadata]!), JSON.stringify({ y: 23 })) assertEq(() => Object.getPrototypeOf(BarOneDec[Symbol.metadata]!), FooOneDec[Symbol.metadata]!) }, // Initializer order 'Initializer order (public members, class statement)': () => { const log: string[] = [] // Class decorators const classDec1 = (cls: { new(): Foo }, ctxClass: ClassDecoratorContext) => { log.push('c2') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c5')) ctxClass.addInitializer(() => log.push('c6')) } const classDec2 = (cls: { new(): Foo }, ctxClass: ClassDecoratorContext) => { log.push('c1') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c3')) ctxClass.addInitializer(() => log.push('c4')) } // Method decorators const methodDec1 = (fn: (this: Foo) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m2') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m5')) ctxMethod.addInitializer(() => log.push('m6')) } const methodDec2 = (fn: (this: Foo) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m1') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m3')) ctxMethod.addInitializer(() => log.push('m4')) } const staticMethodDec1 = (fn: (this: Foo) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M2') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M5')) ctxStaticMethod.addInitializer(() => log.push('M6')) } const staticMethodDec2 = (fn: (this: Foo) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M1') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M3')) ctxStaticMethod.addInitializer(() => log.push('M4')) } // Field decorators const fieldDec1 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Foo, value: undefined) => undefined) | undefined => { log.push('f2') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f5')) ctxField.addInitializer(() => log.push('f6')) return () => { log.push('f7') } } const fieldDec2 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Foo, value: undefined) => undefined) | undefined => { log.push('f1') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f3')) ctxField.addInitializer(() => log.push('f4')) return () => { log.push('f8') } } const staticFieldDec1 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: typeof Foo, value: undefined) => undefined) | undefined => { log.push('F2') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F5')) ctxStaticField.addInitializer(() => log.push('F6')) return () => { log.push('F7') } } const staticFieldDec2 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: typeof Foo, value: undefined) => undefined) | undefined => { log.push('F1') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F3')) ctxStaticField.addInitializer(() => log.push('F4')) return () => { log.push('F8') } } // Getter decorators const getterDec1 = (fn: (this: Foo) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g2') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g5')) ctxGetter.addInitializer(() => log.push('g6')) } const getterDec2 = (fn: (this: Foo) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g1') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g3')) ctxGetter.addInitializer(() => log.push('g4')) } const staticGetterDec1 = (fn: (this: Foo) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G2') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G5')) ctxStaticGetter.addInitializer(() => log.push('G6')) } const staticGetterDec2 = (fn: (this: Foo) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G1') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G3')) ctxStaticGetter.addInitializer(() => log.push('G4')) } // Setter decorators const setterDec1 = (fn: (this: Foo, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s2') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s5')) ctxSetter.addInitializer(() => log.push('s6')) } const setterDec2 = (fn: (this: Foo, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s1') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s3')) ctxSetter.addInitializer(() => log.push('s4')) } const staticSetterDec1 = (fn: (this: Foo, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S2') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S5')) ctxStaticSetter.addInitializer(() => log.push('S6')) } const staticSetterDec2 = (fn: (this: Foo, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S1') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S3')) ctxStaticSetter.addInitializer(() => log.push('S4')) } // Auto-accessor decorators const accessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a2') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a5')) ctxAccessor.addInitializer(() => log.push('a6')) return { init() { log.push('a7') } } } const accessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a1') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a3')) ctxAccessor.addInitializer(() => log.push('a4')) return { init() { log.push('a8') } } } const staticAccessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A2') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A5')) ctxStaticAccessor.addInitializer(() => log.push('A6')) return { init() { log.push('A7') } } } const staticAccessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A1') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A3')) ctxStaticAccessor.addInitializer(() => log.push('A4')) return { init() { log.push('A8') } } } log.push('start') @classDec1 @classDec2 class Foo extends (log.push('extends'), Object) { static { log.push('static:start') } constructor() { log.push('ctor:start') super() log.push('ctor:end') } @methodDec1 @methodDec2 method() { } @staticMethodDec1 @staticMethodDec2 static method() { } @fieldDec1 @fieldDec2 field: undefined @staticFieldDec1 @staticFieldDec2 static field: undefined @getterDec1 @getterDec2 get getter(): undefined { return } @staticGetterDec1 @staticGetterDec2 static get getter(): undefined { return } @setterDec1 @setterDec2 set setter(x: undefined) { } @staticSetterDec1 @staticSetterDec2 static set setter(x: undefined) { } @accessorDec1 @accessorDec2 accessor accessor: undefined @staticAccessorDec1 @staticAccessorDec2 static accessor accessor: undefined static { log.push('static:end') } } log.push('after') new Foo log.push('end') assertEq(() => log + '', 'start,extends,' + 'M1,M2,G1,G2,S1,S2,A1,A2,' + // For each element e of staticElements if e.[[Kind]] is not field 'm1,m2,g1,g2,s1,s2,a1,a2,' + // For each element e of instanceElements if e.[[Kind]] is not field 'F1,F2,' + // For each element e of staticElements if e.[[Kind]] is field 'f1,f2,' + // For each element e of instanceElements if e.[[Kind]] is field 'c1,c2,' + // ApplyDecoratorsToClassDefinition 'M3,M4,M5,M6,G3,G4,G5,G6,S3,S4,S5,S6,' + // For each element initializer of staticMethodExtraInitializers 'static:start,' + // For each element elementRecord of staticElements 'F7,F8,F3,F4,F5,F6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'A7,A8,A3,A4,A5,A6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'static:end,' + // For each element elementRecord of staticElements 'c3,c4,c5,c6,' + // For each element initializer of classExtraInitializers 'after,' + 'ctor:start,' + 'm3,m4,m5,m6,g3,g4,g5,g6,s3,s4,s5,s6,' + // For each element initializer of constructor.[[Initializers]] (a.k.a. instanceMethodExtraInitializers) 'f7,f8,f3,f4,f5,f6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'a7,a8,a3,a4,a5,a6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'ctor:end,' + 'end') }, 'Initializer order (public members, class expression)': () => { const log: string[] = [] // Class decorators const classDec1 = (cls: { new(): Object }, ctxClass: ClassDecoratorContext) => { log.push('c2') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c5')) ctxClass.addInitializer(() => log.push('c6')) } const classDec2 = (cls: { new(): Object }, ctxClass: ClassDecoratorContext) => { log.push('c1') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c3')) ctxClass.addInitializer(() => log.push('c4')) } // Method decorators const methodDec1 = (fn: (this: Object) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m2') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m5')) ctxMethod.addInitializer(() => log.push('m6')) } const methodDec2 = (fn: (this: Object) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m1') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m3')) ctxMethod.addInitializer(() => log.push('m4')) } const staticMethodDec1 = (fn: (this: Object) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M2') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M5')) ctxStaticMethod.addInitializer(() => log.push('M6')) } const staticMethodDec2 = (fn: (this: Object) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M1') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M3')) ctxStaticMethod.addInitializer(() => log.push('M4')) } // Field decorators const fieldDec1 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('f2') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f5')) ctxField.addInitializer(() => log.push('f6')) return () => { log.push('f7') } } const fieldDec2 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('f1') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f3')) ctxField.addInitializer(() => log.push('f4')) return () => { log.push('f8') } } const staticFieldDec1 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('F2') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F5')) ctxStaticField.addInitializer(() => log.push('F6')) return () => { log.push('F7') } } const staticFieldDec2 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('F1') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F3')) ctxStaticField.addInitializer(() => log.push('F4')) return () => { log.push('F8') } } // Getter decorators const getterDec1 = (fn: (this: Object) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g2') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g5')) ctxGetter.addInitializer(() => log.push('g6')) } const getterDec2 = (fn: (this: Object) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g1') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g3')) ctxGetter.addInitializer(() => log.push('g4')) } const staticGetterDec1 = (fn: (this: Object) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G2') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G5')) ctxStaticGetter.addInitializer(() => log.push('G6')) } const staticGetterDec2 = (fn: (this: Object) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G1') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G3')) ctxStaticGetter.addInitializer(() => log.push('G4')) } // Setter decorators const setterDec1 = (fn: (this: Object, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s2') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s5')) ctxSetter.addInitializer(() => log.push('s6')) } const setterDec2 = (fn: (this: Object, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s1') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s3')) ctxSetter.addInitializer(() => log.push('s4')) } const staticSetterDec1 = (fn: (this: Object, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S2') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S5')) ctxStaticSetter.addInitializer(() => log.push('S6')) } const staticSetterDec2 = (fn: (this: Object, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S1') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S3')) ctxStaticSetter.addInitializer(() => log.push('S4')) } // Auto-accessor decorators const accessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a2') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a5')) ctxAccessor.addInitializer(() => log.push('a6')) return { init() { log.push('a7') } } } const accessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a1') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a3')) ctxAccessor.addInitializer(() => log.push('a4')) return { init() { log.push('a8') } } } const staticAccessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A2') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A5')) ctxStaticAccessor.addInitializer(() => log.push('A6')) return { init() { log.push('A7') } } } const staticAccessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A1') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A3')) ctxStaticAccessor.addInitializer(() => log.push('A4')) return { init() { log.push('A8') } } } log.push('start') const Foo = @classDec1 @classDec2 class extends (log.push('extends'), Object) { static { log.push('static:start') } constructor() { log.push('ctor:start') super() log.push('ctor:end') } @methodDec1 @methodDec2 method() { } @staticMethodDec1 @staticMethodDec2 static method() { } @fieldDec1 @fieldDec2 field: undefined @staticFieldDec1 @staticFieldDec2 static field: undefined @getterDec1 @getterDec2 get getter(): undefined { return } @staticGetterDec1 @staticGetterDec2 static get getter(): undefined { return } @setterDec1 @setterDec2 set setter(x: undefined) { } @staticSetterDec1 @staticSetterDec2 static set setter(x: undefined) { } @accessorDec1 @accessorDec2 accessor accessor: undefined @staticAccessorDec1 @staticAccessorDec2 static accessor accessor: undefined static { log.push('static:end') } } log.push('after') new Foo log.push('end') assertEq(() => log + '', 'start,extends,' + 'M1,M2,G1,G2,S1,S2,A1,A2,' + // For each element e of staticElements if e.[[Kind]] is not field 'm1,m2,g1,g2,s1,s2,a1,a2,' + // For each element e of instanceElements if e.[[Kind]] is not field 'F1,F2,' + // For each element e of staticElements if e.[[Kind]] is field 'f1,f2,' + // For each element e of instanceElements if e.[[Kind]] is field 'c1,c2,' + // ApplyDecoratorsToClassDefinition 'M3,M4,M5,M6,G3,G4,G5,G6,S3,S4,S5,S6,' + // For each element initializer of staticMethodExtraInitializers 'static:start,' + // For each element elementRecord of staticElements 'F7,F8,F3,F4,F5,F6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'A7,A8,A3,A4,A5,A6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'static:end,' + // For each element elementRecord of staticElements 'c3,c4,c5,c6,' + // For each element initializer of classExtraInitializers 'after,' + 'ctor:start,' + 'm3,m4,m5,m6,g3,g4,g5,g6,s3,s4,s5,s6,' + // For each element initializer of constructor.[[Initializers]] (a.k.a. instanceMethodExtraInitializers) 'f7,f8,f3,f4,f5,f6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'a7,a8,a3,a4,a5,a6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'ctor:end,' + 'end') }, 'Initializer order (private members, class statement)': () => { const log: string[] = [] // Class decorators const classDec1 = (cls: { new(): Foo }, ctxClass: ClassDecoratorContext) => { log.push('c2') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c5')) ctxClass.addInitializer(() => log.push('c6')) } const classDec2 = (cls: { new(): Foo }, ctxClass: ClassDecoratorContext) => { log.push('c1') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c3')) ctxClass.addInitializer(() => log.push('c4')) } // Method decorators const methodDec1 = (fn: (this: Foo) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m2') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m5')) ctxMethod.addInitializer(() => log.push('m6')) } const methodDec2 = (fn: (this: Foo) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m1') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m3')) ctxMethod.addInitializer(() => log.push('m4')) } const staticMethodDec1 = (fn: (this: Foo) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M2') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M5')) ctxStaticMethod.addInitializer(() => log.push('M6')) } const staticMethodDec2 = (fn: (this: Foo) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M1') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M3')) ctxStaticMethod.addInitializer(() => log.push('M4')) } // Field decorators const fieldDec1 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Foo, value: undefined) => undefined) | undefined => { log.push('f2') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f5')) ctxField.addInitializer(() => log.push('f6')) return () => { log.push('f7') } } const fieldDec2 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Foo, value: undefined) => undefined) | undefined => { log.push('f1') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f3')) ctxField.addInitializer(() => log.push('f4')) return () => { log.push('f8') } } const staticFieldDec1 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: typeof Foo, value: undefined) => undefined) | undefined => { log.push('F2') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F5')) ctxStaticField.addInitializer(() => log.push('F6')) return () => { log.push('F7') } } const staticFieldDec2 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: typeof Foo, value: undefined) => undefined) | undefined => { log.push('F1') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F3')) ctxStaticField.addInitializer(() => log.push('F4')) return () => { log.push('F8') } } // Getter decorators const getterDec1 = (fn: (this: Foo) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g2') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g5')) ctxGetter.addInitializer(() => log.push('g6')) } const getterDec2 = (fn: (this: Foo) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g1') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g3')) ctxGetter.addInitializer(() => log.push('g4')) } const staticGetterDec1 = (fn: (this: Foo) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G2') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G5')) ctxStaticGetter.addInitializer(() => log.push('G6')) } const staticGetterDec2 = (fn: (this: Foo) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G1') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G3')) ctxStaticGetter.addInitializer(() => log.push('G4')) } // Setter decorators const setterDec1 = (fn: (this: Foo, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s2') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s5')) ctxSetter.addInitializer(() => log.push('s6')) } const setterDec2 = (fn: (this: Foo, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s1') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s3')) ctxSetter.addInitializer(() => log.push('s4')) } const staticSetterDec1 = (fn: (this: Foo, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S2') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S5')) ctxStaticSetter.addInitializer(() => log.push('S6')) } const staticSetterDec2 = (fn: (this: Foo, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S1') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S3')) ctxStaticSetter.addInitializer(() => log.push('S4')) } // Auto-accessor decorators const accessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a2') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a5')) ctxAccessor.addInitializer(() => log.push('a6')) return { init() { log.push('a7') } } } const accessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a1') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a3')) ctxAccessor.addInitializer(() => log.push('a4')) return { init() { log.push('a8') } } } const staticAccessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A2') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A5')) ctxStaticAccessor.addInitializer(() => log.push('A6')) return { init() { log.push('A7') } } } const staticAccessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A1') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A3')) ctxStaticAccessor.addInitializer(() => log.push('A4')) return { init() { log.push('A8') } } } log.push('start') @classDec1 @classDec2 class Foo extends (log.push('extends'), Object) { static { log.push('static:start') } constructor() { log.push('ctor:start') super() log.push('ctor:end') } @methodDec1 @methodDec2 #method() { } @staticMethodDec1 @staticMethodDec2 static #staticMethod() { } @fieldDec1 @fieldDec2 #field: undefined @staticFieldDec1 @staticFieldDec2 static #staticField: undefined @getterDec1 @getterDec2 get #getter(): undefined { return } @staticGetterDec1 @staticGetterDec2 static get #staticGetter(): undefined { return } @setterDec1 @setterDec2 set #setter(x: undefined) { } @staticSetterDec1 @staticSetterDec2 static set #staticSetter(x: undefined) { } @accessorDec1 @accessorDec2 accessor #accessor: undefined @staticAccessorDec1 @staticAccessorDec2 static accessor #staticAccessor: undefined static { log.push('static:end') } } log.push('after') new Foo log.push('end') assertEq(() => log + '', 'start,extends,' + 'M1,M2,G1,G2,S1,S2,A1,A2,' + // For each element e of staticElements if e.[[Kind]] is not field 'm1,m2,g1,g2,s1,s2,a1,a2,' + // For each element e of instanceElements if e.[[Kind]] is not field 'F1,F2,' + // For each element e of staticElements if e.[[Kind]] is field 'f1,f2,' + // For each element e of instanceElements if e.[[Kind]] is field 'c1,c2,' + // ApplyDecoratorsToClassDefinition 'M3,M4,M5,M6,G3,G4,G5,G6,S3,S4,S5,S6,' + // For each element initializer of staticMethodExtraInitializers 'static:start,' + // For each element elementRecord of staticElements 'F7,F8,F3,F4,F5,F6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'A7,A8,A3,A4,A5,A6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'static:end,' + // For each element elementRecord of staticElements 'c3,c4,c5,c6,' + // For each element initializer of classExtraInitializers 'after,' + 'ctor:start,' + 'm3,m4,m5,m6,g3,g4,g5,g6,s3,s4,s5,s6,' + // For each element initializer of constructor.[[Initializers]] (a.k.a. instanceMethodExtraInitializers) 'f7,f8,f3,f4,f5,f6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'a7,a8,a3,a4,a5,a6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'ctor:end,' + 'end') }, 'Initializer order (private members, class expression)': () => { const log: string[] = [] // Class decorators const classDec1 = (cls: { new(): Object }, ctxClass: ClassDecoratorContext) => { log.push('c2') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c5')) ctxClass.addInitializer(() => log.push('c6')) } const classDec2 = (cls: { new(): Object }, ctxClass: ClassDecoratorContext) => { log.push('c1') if (!assertEq(() => typeof ctxClass.addInitializer, 'function')) return ctxClass.addInitializer(() => log.push('c3')) ctxClass.addInitializer(() => log.push('c4')) } // Method decorators const methodDec1 = (fn: (this: Object) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m2') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m5')) ctxMethod.addInitializer(() => log.push('m6')) } const methodDec2 = (fn: (this: Object) => void, ctxMethod: ClassMethodDecoratorContext) => { log.push('m1') if (!assertEq(() => typeof ctxMethod.addInitializer, 'function')) return ctxMethod.addInitializer(() => log.push('m3')) ctxMethod.addInitializer(() => log.push('m4')) } const staticMethodDec1 = (fn: (this: Object) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M2') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M5')) ctxStaticMethod.addInitializer(() => log.push('M6')) } const staticMethodDec2 = (fn: (this: Object) => void, ctxStaticMethod: ClassMethodDecoratorContext) => { log.push('M1') if (!assertEq(() => typeof ctxStaticMethod.addInitializer, 'function')) return ctxStaticMethod.addInitializer(() => log.push('M3')) ctxStaticMethod.addInitializer(() => log.push('M4')) } // Field decorators const fieldDec1 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('f2') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f5')) ctxField.addInitializer(() => log.push('f6')) return () => { log.push('f7') } } const fieldDec2 = ( value: undefined, ctxField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('f1') if (!assertEq(() => typeof ctxField.addInitializer, 'function')) return ctxField.addInitializer(() => log.push('f3')) ctxField.addInitializer(() => log.push('f4')) return () => { log.push('f8') } } const staticFieldDec1 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('F2') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F5')) ctxStaticField.addInitializer(() => log.push('F6')) return () => { log.push('F7') } } const staticFieldDec2 = ( value: undefined, ctxStaticField: ClassFieldDecoratorContext, ): ((this: Object, value: undefined) => undefined) | undefined => { log.push('F1') if (!assertEq(() => typeof ctxStaticField.addInitializer, 'function')) return ctxStaticField.addInitializer(() => log.push('F3')) ctxStaticField.addInitializer(() => log.push('F4')) return () => { log.push('F8') } } // Getter decorators const getterDec1 = (fn: (this: Object) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g2') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g5')) ctxGetter.addInitializer(() => log.push('g6')) } const getterDec2 = (fn: (this: Object) => undefined, ctxGetter: ClassGetterDecoratorContext) => { log.push('g1') if (!assertEq(() => typeof ctxGetter.addInitializer, 'function')) return ctxGetter.addInitializer(() => log.push('g3')) ctxGetter.addInitializer(() => log.push('g4')) } const staticGetterDec1 = (fn: (this: Object) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G2') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G5')) ctxStaticGetter.addInitializer(() => log.push('G6')) } const staticGetterDec2 = (fn: (this: Object) => undefined, ctxStaticGetter: ClassGetterDecoratorContext) => { log.push('G1') if (!assertEq(() => typeof ctxStaticGetter.addInitializer, 'function')) return ctxStaticGetter.addInitializer(() => log.push('G3')) ctxStaticGetter.addInitializer(() => log.push('G4')) } // Setter decorators const setterDec1 = (fn: (this: Object, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s2') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s5')) ctxSetter.addInitializer(() => log.push('s6')) } const setterDec2 = (fn: (this: Object, x: undefined) => void, ctxSetter: ClassSetterDecoratorContext) => { log.push('s1') if (!assertEq(() => typeof ctxSetter.addInitializer, 'function')) return ctxSetter.addInitializer(() => log.push('s3')) ctxSetter.addInitializer(() => log.push('s4')) } const staticSetterDec1 = (fn: (this: Object, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S2') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S5')) ctxStaticSetter.addInitializer(() => log.push('S6')) } const staticSetterDec2 = (fn: (this: Object, x: undefined) => void, ctxStaticSetter: ClassSetterDecoratorContext) => { log.push('S1') if (!assertEq(() => typeof ctxStaticSetter.addInitializer, 'function')) return ctxStaticSetter.addInitializer(() => log.push('S3')) ctxStaticSetter.addInitializer(() => log.push('S4')) } // Auto-accessor decorators const accessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a2') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a5')) ctxAccessor.addInitializer(() => log.push('a6')) return { init() { log.push('a7') } } } const accessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('a1') if (!assertEq(() => typeof ctxAccessor.addInitializer, 'function')) return ctxAccessor.addInitializer(() => log.push('a3')) ctxAccessor.addInitializer(() => log.push('a4')) return { init() { log.push('a8') } } } const staticAccessorDec1 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A2') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A5')) ctxStaticAccessor.addInitializer(() => log.push('A6')) return { init() { log.push('A7') } } } const staticAccessorDec2 = ( target: ClassAccessorDecoratorTarget, ctxStaticAccessor: ClassAccessorDecoratorContext, ): ClassAccessorDecoratorResult | undefined => { log.push('A1') if (!assertEq(() => typeof ctxStaticAccessor.addInitializer, 'function')) return ctxStaticAccessor.addInitializer(() => log.push('A3')) ctxStaticAccessor.addInitializer(() => log.push('A4')) return { init() { log.push('A8') } } } log.push('start') const Foo = @classDec1 @classDec2 class extends (log.push('extends'), Object) { static { log.push('static:start') } constructor() { log.push('ctor:start') super() log.push('ctor:end') } @methodDec1 @methodDec2 #method() { } @staticMethodDec1 @staticMethodDec2 static #staticMethod() { } @fieldDec1 @fieldDec2 #field: undefined @staticFieldDec1 @staticFieldDec2 static #staticField: undefined @getterDec1 @getterDec2 get #getter(): undefined { return } @staticGetterDec1 @staticGetterDec2 static get #staticGetter(): undefined { return } @setterDec1 @setterDec2 set #setter(x: undefined) { } @staticSetterDec1 @staticSetterDec2 static set #staticSetter(x: undefined) { } @accessorDec1 @accessorDec2 accessor #accessor: undefined @staticAccessorDec1 @staticAccessorDec2 static accessor #staticAccessor: undefined static { log.push('static:end') } } log.push('after') new Foo log.push('end') assertEq(() => log + '', 'start,extends,' + 'M1,M2,G1,G2,S1,S2,A1,A2,' + // For each element e of staticElements if e.[[Kind]] is not field 'm1,m2,g1,g2,s1,s2,a1,a2,' + // For each element e of instanceElements if e.[[Kind]] is not field 'F1,F2,' + // For each element e of staticElements if e.[[Kind]] is field 'f1,f2,' + // For each element e of instanceElements if e.[[Kind]] is field 'c1,c2,' + // ApplyDecoratorsToClassDefinition 'M3,M4,M5,M6,G3,G4,G5,G6,S3,S4,S5,S6,' + // For each element initializer of staticMethodExtraInitializers 'static:start,' + // For each element elementRecord of staticElements 'F7,F8,F3,F4,F5,F6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'A7,A8,A3,A4,A5,A6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'static:end,' + // For each element elementRecord of staticElements 'c3,c4,c5,c6,' + // For each element initializer of classExtraInitializers 'after,' + 'ctor:start,' + 'm3,m4,m5,m6,g3,g4,g5,g6,s3,s4,s5,s6,' + // For each element initializer of constructor.[[Initializers]] (a.k.a. instanceMethodExtraInitializers) 'f7,f8,f3,f4,f5,f6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'a7,a8,a3,a4,a5,a6,' + // InitializeFieldOrAccessor + For each element initializer of elementRecord.[[ExtraInitializers]] 'ctor:end,' + 'end') }, } function prettyPrint(x: any): any { if (x && x.prototype && x.prototype.constructor === x) return 'class' if (typeof x === 'string') return JSON.stringify(x) try { return x + '' } catch { return 'typeof ' + typeof x // Handle values that don't implement "toString" } } function assertEq(callback: () => T, expected: T): boolean { let details: string try { let x: any = callback() if (x === expected) return true details = ` Expected: ${prettyPrint(expected)}\n Observed: ${prettyPrint(x)}` } catch (error) { details = ` Throws: ${error}` } const code = callback.toString().replace(/^\(\) => /, '').replace(/\s+/g, ' ') console.log(`❌ ${testName}\n Code: ${code}\n${details}\n`) failures++ return false } function assertThrows(callback: () => void, expected: T): boolean { let details: string try { let x: any = callback() details = ` Expected: throws instanceof ${expected.name}\n Observed: returns ${prettyPrint(x)}` } catch (error) { if (error instanceof expected) return true details = ` Expected: throws instanceof ${expected.name}\n Observed: throws ${error}` } const code = callback.toString().replace(/^\(\) => /, '').replace(/\s+/g, ' ') console.log(`❌ ${testName}\n Code: ${code}\n${details}\n`) failures++ return false } let testName: string let failures = 0 async function run() { for (const [name, test] of Object.entries(tests)) { testName = name try { await test() } catch (err) { console.log(`❌ ${name}\n Throws: ${err}\n`) failures++ } } if (failures > 0) { console.log(`❌ ${failures} checks failed`) } else { console.log(`✅ All checks passed`) } } const promise = run()