Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(53461): Implement decorator metadata proposal #54657

Merged
merged 12 commits into from
Jun 23, 2023
23 changes: 15 additions & 8 deletions src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export interface ESDecorateClassContext {
* The name of the decorated element.
*/
name: Expression;

metadata?: Expression;
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -75,6 +77,7 @@ export interface ESDecorateClassElementContext {
static: boolean;
private: boolean;
access: ESDecorateClassElementAccess;
metadata?: Expression;
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
}

/** @internal */
Expand Down Expand Up @@ -245,12 +248,14 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
// ES Decorators Helpers

function createESDecorateClassContextObject(contextIn: ESDecorateClassContext) {
return factory.createObjectLiteralExpression([
const properties = [
factory.createPropertyAssignment(factory.createIdentifier("kind"), factory.createStringLiteral("class")),
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name)
]);
}
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name),
];

if (contextIn.metadata) properties.push(factory.createPropertyAssignment(factory.createIdentifier("metadata"), contextIn.metadata));
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
return factory.createObjectLiteralExpression(properties);
}

function createESDecorateClassElementAccessGetMethod(elementName: ESDecorateName) {
const accessor = elementName.computed ?
Expand Down Expand Up @@ -344,13 +349,16 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
}

function createESDecorateClassElementContextObject(contextIn: ESDecorateClassElementContext) {
return factory.createObjectLiteralExpression([
const properties = [
factory.createPropertyAssignment(factory.createIdentifier("kind"), factory.createStringLiteral(contextIn.kind)),
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name.computed ? contextIn.name.name : factory.createStringLiteralFromNode(contextIn.name.name)),
factory.createPropertyAssignment(factory.createIdentifier("static"), contextIn.static ? factory.createTrue() : factory.createFalse()),
factory.createPropertyAssignment(factory.createIdentifier("private"), contextIn.private ? factory.createTrue() : factory.createFalse()),
factory.createPropertyAssignment(factory.createIdentifier("access"), createESDecorateClassElementAccessObject(contextIn.name, contextIn.access))
]);
factory.createPropertyAssignment(factory.createIdentifier("access"), createESDecorateClassElementAccessObject(contextIn.name, contextIn.access)),
];

if (contextIn.metadata) properties.push(factory.createPropertyAssignment(factory.createIdentifier("metadata"), contextIn.metadata));
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
return factory.createObjectLiteralExpression(properties);
}

function createESDecorateContextObject(contextIn: ESDecorateContext) {
Expand Down Expand Up @@ -381,7 +389,6 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
value ? [thisArg, initializers, value] : [thisArg, initializers]
);
}

// ES2018 Helpers

function createAssignHelper(attributesSegments: Expression[]) {
Expand Down
69 changes: 66 additions & 3 deletions src/compiler/transformers/esDecorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ interface ClassInfo {
classExtraInitializersName?: Identifier; // used in step 13
classThis?: Identifier; // `_classThis`, if needed.
classSuper?: Identifier; // `_classSuper`, if needed.
metadataReference?: Identifier;
rbuckton marked this conversation as resolved.
Show resolved Hide resolved

memberInfos?: Map<ClassElement, MemberInfo>; // used in step 4.a, 12, and construction

Expand Down Expand Up @@ -726,8 +727,11 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
// - The second pass visits the constructor to add instance initializers.
//
// NOTE: If there are no constructors, but there are instance initializers, a synthetic constructor is added.

enterClass(classInfo);

classInfo.metadataReference = factory.createIdentifier("metadata");
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
leadingBlockStatements = append(leadingBlockStatements, createMetadata(classInfo.metadataReference, !!heritageClauses));

let members = visitNodes(node.members, classElementVisitor, isClassElement);
if (pendingExpressions) {
let outerThis: Identifier | undefined;
Expand Down Expand Up @@ -841,7 +845,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
if (classInfo.classDescriptorName && classInfo.classDecoratorsName && classInfo.classExtraInitializersName && classInfo.classThis) {
leadingBlockStatements ??= [];

// __esDecorate(null, _classDescriptor = { value: this }, _classDecorators, { kind: "class", name: this.name }, _classExtraInitializers);
// __esDecorate(null, _classDescriptor = { value: this }, _classDecorators, { kind: "class", name: this.name, metadata }, _classExtraInitializers);
const valueProperty = factory.createPropertyAssignment("value", factory.createThis());
const classDescriptor = factory.createObjectLiteralExpression([valueProperty]);
const classDescriptorAssignment = factory.createAssignment(classInfo.classDescriptorName, classDescriptor);
Expand All @@ -850,7 +854,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
factory.createNull(),
classDescriptorAssignment,
classInfo.classDecoratorsName,
{ kind: "class", name: classNameReference },
{ kind: "class", name: classNameReference, metadata: classInfo.metadataReference },
factory.createNull(),
classInfo.classExtraInitializersName
);
Expand All @@ -865,6 +869,9 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
leadingBlockStatements.push(factory.createExpressionStatement(classReferenceAssignment));
}

// if (metadata) Object.defineProperty(C, Symbol.metadata, { configurable: true, writable: true, value: metadata });
leadingBlockStatements.push(createSymbolMetadata(renamedClassThis, classInfo.metadataReference));

// 11. Static extra initializers are evaluated
if (classInfo.staticExtraInitializersName) {
const runStaticInitializersHelper = emitHelpers().createRunInitializersHelper(renamedClassThis, classInfo.staticExtraInitializersName);
Expand Down Expand Up @@ -1212,6 +1219,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
// 3. If _kind_ is ~field~, ~accessor~, or ~setter~, then ...
set: isPropertyDeclaration(member) || isSetAccessorDeclaration(member)
},
metadata: classInfo.metadataReference,
};

const extraInitializers = isStatic(member) ?
Expand Down Expand Up @@ -2332,4 +2340,59 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
factory.createStringLiteral("default") :
factory.createStringLiteralFromNode(name);
}

function createMetadata(name: Identifier, hasProto: boolean) {
const varDecl = factory.createVariableDeclaration(
name,
/*exclamationToken*/ undefined,
/*type*/ undefined,
factory.createConditionalExpression(
factory.createBinaryExpression(
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
factory.createBinaryExpression(factory.createTypeOfExpression(factory.createIdentifier("Symbol")), factory.createToken(SyntaxKind.EqualsEqualsEqualsToken), factory.createStringLiteral("function")),
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
factory.createToken(SyntaxKind.AmpersandAmpersandToken),
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), factory.createIdentifier("metadata")),
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
),
factory.createToken(SyntaxKind.QuestionToken),
factory.createCallExpression(
factory.createPropertyAccessExpression(factory.createIdentifier("Object"), factory.createIdentifier("create")),
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
/*typeArguments*/ undefined,
[hasProto ? createSymbolMetadataReference() : factory.createNull()]
a-tarasyuk marked this conversation as resolved.
Show resolved Hide resolved
),
factory.createToken(SyntaxKind.ColonToken),
factory.createIdentifier("undefined"),
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
),
);
return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([varDecl], NodeFlags.Const));
}

function createSymbolMetadata(target: Identifier | ThisExpression, value: Identifier) {
const properties = [
factory.createPropertyAssignment("configurable", factory.createTrue()),
factory.createPropertyAssignment("writable", factory.createTrue()),
factory.createPropertyAssignment("enumerable", factory.createTrue()),
factory.createPropertyAssignment("value", value),
];
rbuckton marked this conversation as resolved.
Show resolved Hide resolved

const defineProperty = factory.createCallExpression(
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "defineProperty"),
/*typeArguments*/ undefined,
[
target,
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), factory.createIdentifier("metadata")),
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
factory.createObjectLiteralExpression(properties, /*multiLine*/ false),
]
);

return setEmitFlags(
factory.createIfStatement(value, factory.createExpressionStatement(defineProperty)),
EmitFlags.SingleLine,
);
}

function createSymbolMetadataReference() {
return factory.createElementAccessExpression(
factory.createThis(),
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), factory.createIdentifier("metadata")),
);
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ let C = (() => {
}
},
(() => {
__esDecorate(_a, null, _get_method1_decorators, { kind: "getter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, get: obj => obj.method1 } }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _set_method1_decorators, { kind: "setter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, set: (obj, value) => { obj.method1 = value; } } }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _get_member_decorators, { kind: "getter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, get: obj => obj["method2"] } }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _set_member_decorators, { kind: "setter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, set: (obj, value) => { obj["method2"] = value; } } }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _get_member_decorators_1, { kind: "getter", name: _b, static: false, private: false, access: { has: obj => _b in obj, get: obj => obj[_b] } }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _set_member_decorators_1, { kind: "setter", name: _c, static: false, private: false, access: { has: obj => _c in obj, set: (obj, value) => { obj[_c] = value; } } }, null, _instanceExtraInitializers);
const metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : undefined;
__esDecorate(_a, null, _get_method1_decorators, { kind: "getter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, get: obj => obj.method1 }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _set_method1_decorators, { kind: "setter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, set: (obj, value) => { obj.method1 = value; } }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _get_member_decorators, { kind: "getter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, get: obj => obj["method2"] }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _set_member_decorators, { kind: "setter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, set: (obj, value) => { obj["method2"] = value; } }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _get_member_decorators_1, { kind: "getter", name: _b, static: false, private: false, access: { has: obj => _b in obj, get: obj => obj[_b] }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _set_member_decorators_1, { kind: "setter", name: _c, static: false, private: false, access: { has: obj => _c in obj, set: (obj, value) => { obj[_c] = value; } }, metadata: metadata }, null, _instanceExtraInitializers);
if (metadata) Object.defineProperty(_a, Symbol.metadata, { configurable: true, writable: true, enumerable: true, value: metadata });
})(),
_a;
})();
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ let C = (() => {
let _set_member_decorators_1;
return class C {
static {
__esDecorate(this, null, _get_method1_decorators, { kind: "getter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, get: obj => obj.method1 } }, null, _instanceExtraInitializers);
__esDecorate(this, null, _set_method1_decorators, { kind: "setter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, set: (obj, value) => { obj.method1 = value; } } }, null, _instanceExtraInitializers);
__esDecorate(this, null, _get_member_decorators, { kind: "getter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, get: obj => obj["method2"] } }, null, _instanceExtraInitializers);
__esDecorate(this, null, _set_member_decorators, { kind: "setter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, set: (obj, value) => { obj["method2"] = value; } } }, null, _instanceExtraInitializers);
__esDecorate(this, null, _get_member_decorators_1, { kind: "getter", name: _a, static: false, private: false, access: { has: obj => _a in obj, get: obj => obj[_a] } }, null, _instanceExtraInitializers);
__esDecorate(this, null, _set_member_decorators_1, { kind: "setter", name: _b, static: false, private: false, access: { has: obj => _b in obj, set: (obj, value) => { obj[_b] = value; } } }, null, _instanceExtraInitializers);
const metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : undefined;
__esDecorate(this, null, _get_method1_decorators, { kind: "getter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, get: obj => obj.method1 }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _set_method1_decorators, { kind: "setter", name: "method1", static: false, private: false, access: { has: obj => "method1" in obj, set: (obj, value) => { obj.method1 = value; } }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _get_member_decorators, { kind: "getter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, get: obj => obj["method2"] }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _set_member_decorators, { kind: "setter", name: "method2", static: false, private: false, access: { has: obj => "method2" in obj, set: (obj, value) => { obj["method2"] = value; } }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _get_member_decorators_1, { kind: "getter", name: _a, static: false, private: false, access: { has: obj => _a in obj, get: obj => obj[_a] }, metadata: metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _set_member_decorators_1, { kind: "setter", name: _b, static: false, private: false, access: { has: obj => _b in obj, set: (obj, value) => { obj[_b] = value; } }, metadata: metadata }, null, _instanceExtraInitializers);
if (metadata) Object.defineProperty(this, Symbol.metadata, { configurable: true, writable: true, enumerable: true, value: metadata });
}
get method1() { return 0; }
set method1(value) { }
Expand Down
Loading
Loading