Description
π Search Terms
decorator downlevel
decorator static
π Version & Regression Information
Tested on TypeScript 5.6.3, 5.7.3, 5.8.3 with targets ES2022 and ES2024
β― Playground Link
π» Code
function decorate(target: (abstract new (...args: any[]) => any) & { x: any }) {
console.log('X is ' + target.x);
}
@decorate
class Example {
static x = 'yay'
}
π Actual behavior
Class decorators are run before the class definition is finished executing. In the example, this results in the log message X is undefined
.
π Expected behavior
As per the the stage 3 decorators proposal:
The result of decorators is stored in the equivalent of local variables to be later called after the class definition initially finishes executing.
Consequently, the example should log the message X is yay
Additional information about the issue
The emitted JS places static initialization blocks before the class body from source:
let Example = (() => {
let _classDecorators = [decorate];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var Example = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
Example = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
}
static x = 'yay';
static {
__runInitializers(_classThis, _classExtraInitializers);
}
};
return Example = _classThis;
})();
A potential solution would be to move these blocks after the source code of the class body, like so:
let Example = (() => {
let _classDecorators = [decorate];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var Example = class {
static x = 'yay';
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
Example = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
}
static {
__runInitializers(_classThis, _classExtraInitializers);
}
};
return Example = _classThis;
})();
Also, this does not occur with the ESNext target since decorators are no longer downleveled.