3913 lines
149 KiB
TypeScript
3913 lines
149 KiB
TypeScript
// 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<string, () => Promise<void> | 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<PropertyKey, (this: Foo) => 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<PropertyKey, (this: typeof Foo) => 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<Foo, number>, 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<typeof Foo, number>, 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<Foo, number>, 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<typeof Foo, number>, 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<Foo, number>, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<Foo, number> => {
|
|
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<typeof Foo, number>, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<typeof Foo, number> => {
|
|
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<Foo, number>, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<Foo, number> => {
|
|
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<typeof Foo, number>, ctx: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<typeof Foo, number> => {
|
|
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<Foo, undefined>, 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<typeof Foo, undefined>, 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<Foo, undefined>, 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<typeof Foo, undefined>, 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<Foo, undefined>, 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<typeof Foo, undefined>, 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<Foo, undefined>, 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<typeof Foo, undefined>, 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<Foo, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Foo, undefined> | 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<Foo, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Foo, undefined> | 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<typeof Foo, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<typeof Foo, undefined> | 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<typeof Foo, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<typeof Foo, undefined> | 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<Object, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Object, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Object, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Object, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Foo, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Foo, undefined> | 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<Foo, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Foo, undefined> | 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<typeof Foo, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<typeof Foo, undefined> | 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<typeof Foo, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<typeof Foo, undefined> | 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<Object, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Object, undefined>,
|
|
ctxAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Object, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<Object, undefined>,
|
|
ctxStaticAccessor: ClassAccessorDecoratorContext,
|
|
): ClassAccessorDecoratorResult<Object, undefined> | 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<T>(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<T extends Function>(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()
|