Skip to content

Commit bc91920

Browse files
authored
feat(53461): Implement decorator metadata proposal (#54657)
1 parent f7e4f80 commit bc91920

File tree

276 files changed

+2926
-973
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

276 files changed

+2926
-973
lines changed

src/compiler/commandLineParser.ts

+1
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ const libEntries: [string, string][] = [
223223
["esnext.string", "lib.es2022.string.d.ts"],
224224
["esnext.promise", "lib.es2021.promise.d.ts"],
225225
["esnext.weakref", "lib.es2021.weakref.d.ts"],
226+
["esnext.decorators", "lib.esnext.decorators.d.ts"],
226227
["decorators", "lib.decorators.d.ts"],
227228
["decorators.legacy", "lib.decorators.legacy.d.ts"],
228229
];

src/compiler/factory/emitHelpers.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export interface ESDecorateClassContext {
5959
* The name of the decorated element.
6060
*/
6161
name: Expression;
62+
63+
metadata: Expression;
6264
}
6365

6466
/**
@@ -75,6 +77,7 @@ export interface ESDecorateClassElementContext {
7577
static: boolean;
7678
private: boolean;
7779
access: ESDecorateClassElementAccess;
80+
metadata: Expression;
7881
}
7982

8083
/** @internal */
@@ -251,12 +254,14 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
251254
// ES Decorators Helpers
252255

253256
function createESDecorateClassContextObject(contextIn: ESDecorateClassContext) {
254-
return factory.createObjectLiteralExpression([
257+
const properties = [
255258
factory.createPropertyAssignment(factory.createIdentifier("kind"), factory.createStringLiteral("class")),
256-
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name)
257-
]);
258-
}
259+
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name),
260+
factory.createPropertyAssignment(factory.createIdentifier("metadata"), contextIn.metadata),
261+
];
259262

263+
return factory.createObjectLiteralExpression(properties);
264+
}
260265

261266
function createESDecorateClassElementAccessGetMethod(elementName: ESDecorateName) {
262267
const accessor = elementName.computed ?
@@ -350,13 +355,15 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
350355
}
351356

352357
function createESDecorateClassElementContextObject(contextIn: ESDecorateClassElementContext) {
353-
return factory.createObjectLiteralExpression([
358+
const properties = [
354359
factory.createPropertyAssignment(factory.createIdentifier("kind"), factory.createStringLiteral(contextIn.kind)),
355360
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name.computed ? contextIn.name.name : factory.createStringLiteralFromNode(contextIn.name.name)),
356361
factory.createPropertyAssignment(factory.createIdentifier("static"), contextIn.static ? factory.createTrue() : factory.createFalse()),
357362
factory.createPropertyAssignment(factory.createIdentifier("private"), contextIn.private ? factory.createTrue() : factory.createFalse()),
358-
factory.createPropertyAssignment(factory.createIdentifier("access"), createESDecorateClassElementAccessObject(contextIn.name, contextIn.access))
359-
]);
363+
factory.createPropertyAssignment(factory.createIdentifier("access"), createESDecorateClassElementAccessObject(contextIn.name, contextIn.access)),
364+
factory.createPropertyAssignment(factory.createIdentifier("metadata"), contextIn.metadata),
365+
];
366+
return factory.createObjectLiteralExpression(properties);
360367
}
361368

362369
function createESDecorateContextObject(contextIn: ESDecorateContext) {
@@ -387,7 +394,6 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
387394
value ? [thisArg, initializers, value] : [thisArg, initializers]
388395
);
389396
}
390-
391397
// ES2018 Helpers
392398

393399
function createAssignHelper(attributesSegments: Expression[]) {

src/compiler/transformers/classFields.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1936,9 +1936,8 @@ export function transformClassFields(context: TransformationContext): (x: Source
19361936
}
19371937

19381938
const classCheckFlags = resolver.getNodeCheckFlags(node);
1939-
const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference;
19401939
const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop;
1941-
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference);
1940+
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, /*reservedInNestedScopes*/ true);
19421941
getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp);
19431942
return temp;
19441943
}

src/compiler/transformers/esDecorators.ts

+73-37
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ interface ClassInfo {
228228
classExtraInitializersName?: Identifier; // used in step 13
229229
classThis?: Identifier; // `_classThis`, if needed.
230230
classSuper?: Identifier; // `_classSuper`, if needed.
231+
metadataReference: Identifier;
231232

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

@@ -563,6 +564,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
563564
}
564565

565566
function createClassInfo(node: ClassLikeDeclaration): ClassInfo {
567+
const metadataReference = factory.createUniqueName("_metadata", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel);
566568
let instanceExtraInitializersName: Identifier | undefined;
567569
let staticExtraInitializersName: Identifier | undefined;
568570
let hasStaticInitializers = false;
@@ -611,6 +613,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
611613

612614
return {
613615
class: node,
616+
metadataReference,
614617
instanceExtraInitializersName,
615618
staticExtraInitializersName,
616619
hasStaticInitializers,
@@ -619,18 +622,6 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
619622
};
620623
}
621624

622-
function containsLexicalSuperInStaticInitializer(node: ClassLikeDeclaration) {
623-
for (const member of node.members) {
624-
if (isClassStaticBlockDeclaration(member) ||
625-
isPropertyDeclaration(member) && hasStaticModifier(member)) {
626-
if (member.transformFlags & TransformFlags.ContainsLexicalSuper) {
627-
return true;
628-
}
629-
}
630-
}
631-
return false;
632-
}
633-
634625
function transformClassLike(node: ClassLikeDeclaration) {
635626
startLexicalEnvironment();
636627

@@ -681,31 +672,25 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
681672
}
682673

683674
// Rewrite `super` in static initializers so that we can use the correct `this`.
684-
if (classDecorators && containsLexicalSuperInStaticInitializer(node)) {
685-
const extendsClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
686-
const extendsElement = extendsClause && firstOrUndefined(extendsClause.types);
687-
const extendsExpression = extendsElement && visitNode(extendsElement.expression, visitor, isExpression);
688-
if (extendsExpression) {
689-
classInfo.classSuper = factory.createUniqueName("_classSuper", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel);
675+
const extendsClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
676+
const extendsElement = extendsClause && firstOrUndefined(extendsClause.types);
677+
const extendsExpression = extendsElement && visitNode(extendsElement.expression, visitor, isExpression);
678+
if (extendsExpression) {
679+
classInfo.classSuper = factory.createUniqueName("_classSuper", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel);
690680

691681
// Ensure we do not give the class or function an assigned name due to the variable by prefixing it
692682
// with `0, `.
693-
const unwrapped = skipOuterExpressions(extendsExpression);
694-
const safeExtendsExpression =
695-
isClassExpression(unwrapped) && !unwrapped.name ||
696-
isFunctionExpression(unwrapped) && !unwrapped.name ||
697-
isArrowFunction(unwrapped) ?
698-
factory.createComma(factory.createNumericLiteral(0), extendsExpression) :
699-
extendsExpression;
700-
classDefinitionStatements.push(createLet(classInfo.classSuper, safeExtendsExpression));
701-
const updatedExtendsElement = factory.updateExpressionWithTypeArguments(extendsElement, classInfo.classSuper, /*typeArguments*/ undefined);
702-
const updatedExtendsClause = factory.updateHeritageClause(extendsClause, [updatedExtendsElement]);
703-
heritageClauses = factory.createNodeArray([updatedExtendsClause]);
704-
}
705-
}
706-
else {
707-
// 2. ClassHeritage clause is evaluated outside of the private name scope of the class.
708-
heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
683+
const unwrapped = skipOuterExpressions(extendsExpression);
684+
const safeExtendsExpression =
685+
isClassExpression(unwrapped) && !unwrapped.name ||
686+
isFunctionExpression(unwrapped) && !unwrapped.name ||
687+
isArrowFunction(unwrapped) ?
688+
factory.createComma(factory.createNumericLiteral(0), extendsExpression) :
689+
extendsExpression;
690+
classDefinitionStatements.push(createLet(classInfo.classSuper, safeExtendsExpression));
691+
const updatedExtendsElement = factory.updateExpressionWithTypeArguments(extendsElement, classInfo.classSuper, /*typeArguments*/ undefined);
692+
const updatedExtendsClause = factory.updateHeritageClause(extendsClause, [updatedExtendsElement]);
693+
heritageClauses = factory.createNodeArray([updatedExtendsClause]);
709694
}
710695

711696
const renamedClassThis = classInfo.classThis ?? factory.createThis();
@@ -724,8 +709,10 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
724709
// - The second pass visits the constructor to add instance initializers.
725710
//
726711
// NOTE: If there are no constructors, but there are instance initializers, a synthetic constructor is added.
727-
728712
enterClass(classInfo);
713+
714+
leadingBlockStatements = append(leadingBlockStatements, createMetadata(classInfo.metadataReference, classInfo.classSuper));
715+
729716
let members = visitNodes(node.members, classElementVisitor, isClassElement);
730717
if (pendingExpressions) {
731718
let outerThis: Identifier | undefined;
@@ -840,7 +827,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
840827
leadingBlockStatements ??= [];
841828

842829
// produces:
843-
// __esDecorate(null, _classDescriptor = { value: this }, _classDecorators, { kind: "class", name: this.name }, _classExtraInitializers);
830+
// __esDecorate(null, _classDescriptor = { value: this }, _classDecorators, { kind: "class", name: this.name, metadata }, _classExtraInitializers);
844831
const valueProperty = factory.createPropertyAssignment("value", renamedClassThis);
845832
const classDescriptor = factory.createObjectLiteralExpression([valueProperty]);
846833
const classDescriptorAssignment = factory.createAssignment(classInfo.classDescriptorName, classDescriptor);
@@ -849,7 +836,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
849836
factory.createNull(),
850837
classDescriptorAssignment,
851838
classInfo.classDecoratorsName,
852-
{ kind: "class", name: classNameReference },
839+
{ kind: "class", name: classNameReference, metadata: classInfo.metadataReference },
853840
factory.createNull(),
854841
classInfo.classExtraInitializersName
855842
);
@@ -865,6 +852,9 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
865852
leadingBlockStatements.push(factory.createExpressionStatement(classReferenceAssignment));
866853
}
867854

855+
// if (metadata) Object.defineProperty(C, Symbol.metadata, { configurable: true, writable: true, value: metadata });
856+
leadingBlockStatements.push(createSymbolMetadata(renamedClassThis, classInfo.metadataReference));
857+
868858
// 11. Static extra initializers are evaluated
869859
if (classInfo.staticExtraInitializersName) {
870860
const runStaticInitializersHelper = emitHelpers().createRunInitializersHelper(renamedClassThis, classInfo.staticExtraInitializersName);
@@ -1283,6 +1273,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
12831273
// 3. If _kind_ is ~field~, ~accessor~, or ~setter~, then ...
12841274
set: isPropertyDeclaration(member) || isSetAccessorDeclaration(member)
12851275
},
1276+
metadata: classInfo.metadataReference,
12861277
};
12871278

12881279
const extraInitializers = isStatic(member) ?
@@ -2371,4 +2362,49 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
23712362
])
23722363
);
23732364
}
2365+
function createMetadata(name: Identifier, classSuper: Identifier | undefined) {
2366+
const varDecl = factory.createVariableDeclaration(
2367+
name,
2368+
/*exclamationToken*/ undefined,
2369+
/*type*/ undefined,
2370+
factory.createConditionalExpression(
2371+
factory.createLogicalAnd(
2372+
factory.createTypeCheck(factory.createIdentifier("Symbol"), "function"),
2373+
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), "metadata"),
2374+
),
2375+
factory.createToken(SyntaxKind.QuestionToken),
2376+
factory.createCallExpression(
2377+
factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "create"),
2378+
/*typeArguments*/ undefined,
2379+
[classSuper ? createSymbolMetadataReference(classSuper) : factory.createNull()]
2380+
),
2381+
factory.createToken(SyntaxKind.ColonToken),
2382+
factory.createVoidZero(),
2383+
),
2384+
);
2385+
return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([varDecl], NodeFlags.Const));
2386+
}
2387+
2388+
function createSymbolMetadata(target: Identifier | ThisExpression, value: Identifier) {
2389+
const defineProperty = factory.createObjectDefinePropertyCall(
2390+
target,
2391+
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), "metadata"),
2392+
factory.createPropertyDescriptor({ configurable: true, writable: true, enumerable: true, value }, /*singleLine*/ true)
2393+
);
2394+
return setEmitFlags(
2395+
factory.createIfStatement(value, factory.createExpressionStatement(defineProperty)),
2396+
EmitFlags.SingleLine,
2397+
);
2398+
}
2399+
2400+
function createSymbolMetadataReference(classSuper: Identifier) {
2401+
return factory.createBinaryExpression(
2402+
factory.createElementAccessExpression(
2403+
classSuper,
2404+
factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), "metadata"),
2405+
),
2406+
SyntaxKind.QuestionQuestionToken,
2407+
factory.createNull()
2408+
);
2409+
}
23742410
}

src/harness/evaluatorImpl.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ for (const key of Object.getOwnPropertyNames(Symbol)) {
2020
const symbolNames = [
2121
"asyncIterator",
2222
"dispose",
23-
"asyncDispose"
23+
"asyncDispose",
24+
"metadata",
2425
];
2526

2627
for (const symbolName of symbolNames) {
2728
if (!ts.hasProperty(FakeSymbol, symbolName)) {
2829
Object.defineProperty(FakeSymbol, symbolName, {
2930
value: Symbol.for(`Symbol.${symbolName}`),
30-
configurable: true
31+
configurable: true,
3132
});
3233
}
3334
}

src/harness/fourslashInterfaceImpl.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,8 @@ export namespace Completion {
11681168
typeEntry("ParameterDecorator"),
11691169
typeEntry("ClassMemberDecoratorContext"),
11701170
typeEntry("DecoratorContext"),
1171+
typeEntry("DecoratorMetadata"),
1172+
typeEntry("DecoratorMetadataObject"),
11711173
interfaceEntry("ClassDecoratorContext"),
11721174
interfaceEntry("ClassMethodDecoratorContext"),
11731175
interfaceEntry("ClassGetterDecoratorContext"),

src/lib/decorators.d.ts

+17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ type DecoratorContext =
1717
| ClassMemberDecoratorContext
1818
;
1919

20+
type DecoratorMetadataObject = Record<PropertyKey, unknown> & object;
21+
22+
type DecoratorMetadata =
23+
typeof globalThis extends { Symbol: { readonly metadata: symbol } } ? DecoratorMetadataObject : DecoratorMetadataObject | undefined;
24+
2025
/**
2126
* Context provided to a class decorator.
2227
* @template Class The type of the decorated class associated with this context.
@@ -48,6 +53,8 @@ interface ClassDecoratorContext<
4853
* ```
4954
*/
5055
addInitializer(initializer: (this: Class) => void): void;
56+
57+
readonly metadata: DecoratorMetadata;
5158
}
5259

5360
/**
@@ -112,6 +119,8 @@ interface ClassMethodDecoratorContext<
112119
* ```
113120
*/
114121
addInitializer(initializer: (this: This) => void): void;
122+
123+
readonly metadata: DecoratorMetadata;
115124
}
116125

117126
/**
@@ -157,6 +166,8 @@ interface ClassGetterDecoratorContext<
157166
* decorating a non-`static` element).
158167
*/
159168
addInitializer(initializer: (this: This) => void): void;
169+
170+
readonly metadata: DecoratorMetadata;
160171
}
161172

162173
/**
@@ -202,6 +213,8 @@ interface ClassSetterDecoratorContext<
202213
* decorating a non-`static` element).
203214
*/
204215
addInitializer(initializer: (this: This) => void): void;
216+
217+
readonly metadata: DecoratorMetadata;
205218
}
206219

207220
/**
@@ -256,6 +269,8 @@ interface ClassAccessorDecoratorContext<
256269
* decorating a non-`static` element).
257270
*/
258271
addInitializer(initializer: (this: This) => void): void;
272+
273+
readonly metadata: DecoratorMetadata;
259274
}
260275

261276
/**
@@ -351,4 +366,6 @@ interface ClassFieldDecoratorContext<
351366
* decorating a non-`static` element).
352367
*/
353368
addInitializer(initializer: (this: This) => void): void;
369+
370+
readonly metadata: DecoratorMetadata;
354371
}

src/lib/esnext.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/// <reference lib="es2023" />
22
/// <reference lib="esnext.intl" />
3+
/// <reference lib="esnext.decorators" />
34
/// <reference lib="esnext.disposable" />

src/lib/esnext.decorators.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference lib="es2015.symbol" />
2+
/// <reference lib="decorators" />
3+
4+
interface SymbolConstructor {
5+
readonly metadata: unique symbol;
6+
}
7+
8+
interface Function {
9+
[Symbol.metadata]: DecoratorMetadata | null;
10+
}

src/lib/libs.json

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"es2022.regexp",
6868
"es2023.array",
6969
"es2023.collection",
70+
"esnext.decorators",
7071
"esnext.intl",
7172
"esnext.disposable",
7273
"decorators",

src/testRunner/tests.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import "./unittests/evaluation/awaiter";
3737
import "./unittests/evaluation/destructuring";
3838
import "./unittests/evaluation/externalModules";
3939
import "./unittests/evaluation/esDecorators";
40+
import "./unittests/evaluation/esDecoratorsMetadata";
4041
import "./unittests/evaluation/forAwaitOf";
4142
import "./unittests/evaluation/forOf";
4243
import "./unittests/evaluation/generator";

0 commit comments

Comments
 (0)