mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
feat(53461): Implement decorator metadata proposal (#54657)
This commit is contained in:
@@ -223,6 +223,7 @@ const libEntries: [string, string][] = [
|
||||
["esnext.string", "lib.es2022.string.d.ts"],
|
||||
["esnext.promise", "lib.es2021.promise.d.ts"],
|
||||
["esnext.weakref", "lib.es2021.weakref.d.ts"],
|
||||
["esnext.decorators", "lib.esnext.decorators.d.ts"],
|
||||
["decorators", "lib.decorators.d.ts"],
|
||||
["decorators.legacy", "lib.decorators.legacy.d.ts"],
|
||||
];
|
||||
|
||||
@@ -59,6 +59,8 @@ export interface ESDecorateClassContext {
|
||||
* The name of the decorated element.
|
||||
*/
|
||||
name: Expression;
|
||||
|
||||
metadata: Expression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,6 +77,7 @@ export interface ESDecorateClassElementContext {
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
access: ESDecorateClassElementAccess;
|
||||
metadata: Expression;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -251,12 +254,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),
|
||||
factory.createPropertyAssignment(factory.createIdentifier("metadata"), contextIn.metadata),
|
||||
];
|
||||
|
||||
return factory.createObjectLiteralExpression(properties);
|
||||
}
|
||||
|
||||
function createESDecorateClassElementAccessGetMethod(elementName: ESDecorateName) {
|
||||
const accessor = elementName.computed ?
|
||||
@@ -350,13 +355,15 @@ 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)),
|
||||
factory.createPropertyAssignment(factory.createIdentifier("metadata"), contextIn.metadata),
|
||||
];
|
||||
return factory.createObjectLiteralExpression(properties);
|
||||
}
|
||||
|
||||
function createESDecorateContextObject(contextIn: ESDecorateContext) {
|
||||
@@ -387,7 +394,6 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
|
||||
value ? [thisArg, initializers, value] : [thisArg, initializers]
|
||||
);
|
||||
}
|
||||
|
||||
// ES2018 Helpers
|
||||
|
||||
function createAssignHelper(attributesSegments: Expression[]) {
|
||||
|
||||
@@ -1936,9 +1936,8 @@ export function transformClassFields(context: TransformationContext): (x: Source
|
||||
}
|
||||
|
||||
const classCheckFlags = resolver.getNodeCheckFlags(node);
|
||||
const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference;
|
||||
const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop;
|
||||
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference);
|
||||
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, /*reservedInNestedScopes*/ true);
|
||||
getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp);
|
||||
return temp;
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ interface ClassInfo {
|
||||
classExtraInitializersName?: Identifier; // used in step 13
|
||||
classThis?: Identifier; // `_classThis`, if needed.
|
||||
classSuper?: Identifier; // `_classSuper`, if needed.
|
||||
metadataReference: Identifier;
|
||||
|
||||
memberInfos?: Map<ClassElement, MemberInfo>; // used in step 4.a, 12, and construction
|
||||
|
||||
@@ -563,6 +564,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
|
||||
}
|
||||
|
||||
function createClassInfo(node: ClassLikeDeclaration): ClassInfo {
|
||||
const metadataReference = factory.createUniqueName("_metadata", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel);
|
||||
let instanceExtraInitializersName: Identifier | undefined;
|
||||
let staticExtraInitializersName: Identifier | undefined;
|
||||
let hasStaticInitializers = false;
|
||||
@@ -611,6 +613,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
|
||||
|
||||
return {
|
||||
class: node,
|
||||
metadataReference,
|
||||
instanceExtraInitializersName,
|
||||
staticExtraInitializersName,
|
||||
hasStaticInitializers,
|
||||
@@ -619,18 +622,6 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
|
||||
};
|
||||
}
|
||||
|
||||
function containsLexicalSuperInStaticInitializer(node: ClassLikeDeclaration) {
|
||||
for (const member of node.members) {
|
||||
if (isClassStaticBlockDeclaration(member) ||
|
||||
isPropertyDeclaration(member) && hasStaticModifier(member)) {
|
||||
if (member.transformFlags & TransformFlags.ContainsLexicalSuper) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function transformClassLike(node: ClassLikeDeclaration) {
|
||||
startLexicalEnvironment();
|
||||
|
||||
@@ -681,31 +672,25 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
|
||||
}
|
||||
|
||||
// Rewrite `super` in static initializers so that we can use the correct `this`.
|
||||
if (classDecorators && containsLexicalSuperInStaticInitializer(node)) {
|
||||
const extendsClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
|
||||
const extendsElement = extendsClause && firstOrUndefined(extendsClause.types);
|
||||
const extendsExpression = extendsElement && visitNode(extendsElement.expression, visitor, isExpression);
|
||||
if (extendsExpression) {
|
||||
classInfo.classSuper = factory.createUniqueName("_classSuper", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel);
|
||||
const extendsClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
|
||||
const extendsElement = extendsClause && firstOrUndefined(extendsClause.types);
|
||||
const extendsExpression = extendsElement && visitNode(extendsElement.expression, visitor, isExpression);
|
||||
if (extendsExpression) {
|
||||
classInfo.classSuper = factory.createUniqueName("_classSuper", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel);
|
||||
|
||||
// Ensure we do not give the class or function an assigned name due to the variable by prefixing it
|
||||
// with `0, `.
|
||||
const unwrapped = skipOuterExpressions(extendsExpression);
|
||||
const safeExtendsExpression =
|
||||
isClassExpression(unwrapped) && !unwrapped.name ||
|
||||
isFunctionExpression(unwrapped) && !unwrapped.name ||
|
||||
isArrowFunction(unwrapped) ?
|
||||
factory.createComma(factory.createNumericLiteral(0), extendsExpression) :
|
||||
extendsExpression;
|
||||
classDefinitionStatements.push(createLet(classInfo.classSuper, safeExtendsExpression));
|
||||
const updatedExtendsElement = factory.updateExpressionWithTypeArguments(extendsElement, classInfo.classSuper, /*typeArguments*/ undefined);
|
||||
const updatedExtendsClause = factory.updateHeritageClause(extendsClause, [updatedExtendsElement]);
|
||||
heritageClauses = factory.createNodeArray([updatedExtendsClause]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 2. ClassHeritage clause is evaluated outside of the private name scope of the class.
|
||||
heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
|
||||
const unwrapped = skipOuterExpressions(extendsExpression);
|
||||
const safeExtendsExpression =
|
||||
isClassExpression(unwrapped) && !unwrapped.name ||
|
||||
isFunctionExpression(unwrapped) && !unwrapped.name ||
|
||||
isArrowFunction(unwrapped) ?
|
||||
factory.createComma(factory.createNumericLiteral(0), extendsExpression) :
|
||||
extendsExpression;
|
||||
classDefinitionStatements.push(createLet(classInfo.classSuper, safeExtendsExpression));
|
||||
const updatedExtendsElement = factory.updateExpressionWithTypeArguments(extendsElement, classInfo.classSuper, /*typeArguments*/ undefined);
|
||||
const updatedExtendsClause = factory.updateHeritageClause(extendsClause, [updatedExtendsElement]);
|
||||
heritageClauses = factory.createNodeArray([updatedExtendsClause]);
|
||||
}
|
||||
|
||||
const renamedClassThis = classInfo.classThis ?? factory.createThis();
|
||||
@@ -724,8 +709,10 @@ 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);
|
||||
|
||||
leadingBlockStatements = append(leadingBlockStatements, createMetadata(classInfo.metadataReference, classInfo.classSuper));
|
||||
|
||||
let members = visitNodes(node.members, classElementVisitor, isClassElement);
|
||||
if (pendingExpressions) {
|
||||
let outerThis: Identifier | undefined;
|
||||
@@ -840,7 +827,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
|
||||
leadingBlockStatements ??= [];
|
||||
|
||||
// produces:
|
||||
// __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", renamedClassThis);
|
||||
const classDescriptor = factory.createObjectLiteralExpression([valueProperty]);
|
||||
const classDescriptorAssignment = factory.createAssignment(classInfo.classDescriptorName, classDescriptor);
|
||||
@@ -849,7 +836,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
|
||||
);
|
||||
@@ -865,6 +852,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);
|
||||
@@ -1283,6 +1273,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) ?
|
||||
@@ -2371,4 +2362,49 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
|
||||
])
|
||||
);
|
||||
}
|
||||
function createMetadata(name: Identifier, classSuper: Identifier | undefined) {
|
||||
const varDecl = factory.createVariableDeclaration(
|
||||
name,
|
||||
/*exclamationToken*/ undefined,
|
||||
/*type*/ undefined,
|
||||
factory.createConditionalExpression(
|
||||
factory.createLogicalAnd(
|
||||
factory.createTypeCheck(factory.createIdentifier("Symbol"), "function"),
|
||||
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), "metadata"),
|
||||
),
|
||||
factory.createToken(SyntaxKind.QuestionToken),
|
||||
factory.createCallExpression(
|
||||
factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "create"),
|
||||
/*typeArguments*/ undefined,
|
||||
[classSuper ? createSymbolMetadataReference(classSuper) : factory.createNull()]
|
||||
),
|
||||
factory.createToken(SyntaxKind.ColonToken),
|
||||
factory.createVoidZero(),
|
||||
),
|
||||
);
|
||||
return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([varDecl], NodeFlags.Const));
|
||||
}
|
||||
|
||||
function createSymbolMetadata(target: Identifier | ThisExpression, value: Identifier) {
|
||||
const defineProperty = factory.createObjectDefinePropertyCall(
|
||||
target,
|
||||
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), "metadata"),
|
||||
factory.createPropertyDescriptor({ configurable: true, writable: true, enumerable: true, value }, /*singleLine*/ true)
|
||||
);
|
||||
return setEmitFlags(
|
||||
factory.createIfStatement(value, factory.createExpressionStatement(defineProperty)),
|
||||
EmitFlags.SingleLine,
|
||||
);
|
||||
}
|
||||
|
||||
function createSymbolMetadataReference(classSuper: Identifier) {
|
||||
return factory.createBinaryExpression(
|
||||
factory.createElementAccessExpression(
|
||||
classSuper,
|
||||
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), "metadata"),
|
||||
),
|
||||
SyntaxKind.QuestionQuestionToken,
|
||||
factory.createNull()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user