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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,15 @@ for (const key of Object.getOwnPropertyNames(Symbol)) {
|
||||
const symbolNames = [
|
||||
"asyncIterator",
|
||||
"dispose",
|
||||
"asyncDispose"
|
||||
"asyncDispose",
|
||||
"metadata",
|
||||
];
|
||||
|
||||
for (const symbolName of symbolNames) {
|
||||
if (!ts.hasProperty(FakeSymbol, symbolName)) {
|
||||
Object.defineProperty(FakeSymbol, symbolName, {
|
||||
value: Symbol.for(`Symbol.${symbolName}`),
|
||||
configurable: true
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,6 +1168,8 @@ export namespace Completion {
|
||||
typeEntry("ParameterDecorator"),
|
||||
typeEntry("ClassMemberDecoratorContext"),
|
||||
typeEntry("DecoratorContext"),
|
||||
typeEntry("DecoratorMetadata"),
|
||||
typeEntry("DecoratorMetadataObject"),
|
||||
interfaceEntry("ClassDecoratorContext"),
|
||||
interfaceEntry("ClassMethodDecoratorContext"),
|
||||
interfaceEntry("ClassGetterDecoratorContext"),
|
||||
|
||||
Vendored
+17
@@ -17,6 +17,11 @@ type DecoratorContext =
|
||||
| ClassMemberDecoratorContext
|
||||
;
|
||||
|
||||
type DecoratorMetadataObject = Record<PropertyKey, unknown> & object;
|
||||
|
||||
type DecoratorMetadata =
|
||||
typeof globalThis extends { Symbol: { readonly metadata: symbol } } ? DecoratorMetadataObject : DecoratorMetadataObject | undefined;
|
||||
|
||||
/**
|
||||
* Context provided to a class decorator.
|
||||
* @template Class The type of the decorated class associated with this context.
|
||||
@@ -48,6 +53,8 @@ interface ClassDecoratorContext<
|
||||
* ```
|
||||
*/
|
||||
addInitializer(initializer: (this: Class) => void): void;
|
||||
|
||||
readonly metadata: DecoratorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +119,8 @@ interface ClassMethodDecoratorContext<
|
||||
* ```
|
||||
*/
|
||||
addInitializer(initializer: (this: This) => void): void;
|
||||
|
||||
readonly metadata: DecoratorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +166,8 @@ interface ClassGetterDecoratorContext<
|
||||
* decorating a non-`static` element).
|
||||
*/
|
||||
addInitializer(initializer: (this: This) => void): void;
|
||||
|
||||
readonly metadata: DecoratorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,6 +213,8 @@ interface ClassSetterDecoratorContext<
|
||||
* decorating a non-`static` element).
|
||||
*/
|
||||
addInitializer(initializer: (this: This) => void): void;
|
||||
|
||||
readonly metadata: DecoratorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,6 +269,8 @@ interface ClassAccessorDecoratorContext<
|
||||
* decorating a non-`static` element).
|
||||
*/
|
||||
addInitializer(initializer: (this: This) => void): void;
|
||||
|
||||
readonly metadata: DecoratorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,4 +366,6 @@ interface ClassFieldDecoratorContext<
|
||||
* decorating a non-`static` element).
|
||||
*/
|
||||
addInitializer(initializer: (this: This) => void): void;
|
||||
|
||||
readonly metadata: DecoratorMetadata;
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
/// <reference lib="es2023" />
|
||||
/// <reference lib="esnext.intl" />
|
||||
/// <reference lib="esnext.decorators" />
|
||||
/// <reference lib="esnext.disposable" />
|
||||
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/// <reference lib="es2015.symbol" />
|
||||
/// <reference lib="decorators" />
|
||||
|
||||
interface SymbolConstructor {
|
||||
readonly metadata: unique symbol;
|
||||
}
|
||||
|
||||
interface Function {
|
||||
[Symbol.metadata]: DecoratorMetadata | null;
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
"es2022.regexp",
|
||||
"es2023.array",
|
||||
"es2023.collection",
|
||||
"esnext.decorators",
|
||||
"esnext.intl",
|
||||
"esnext.disposable",
|
||||
"decorators",
|
||||
|
||||
@@ -37,6 +37,7 @@ import "./unittests/evaluation/awaiter";
|
||||
import "./unittests/evaluation/destructuring";
|
||||
import "./unittests/evaluation/externalModules";
|
||||
import "./unittests/evaluation/esDecorators";
|
||||
import "./unittests/evaluation/esDecoratorsMetadata";
|
||||
import "./unittests/evaluation/forAwaitOf";
|
||||
import "./unittests/evaluation/forOf";
|
||||
import "./unittests/evaluation/generator";
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import * as evaluator from "../../_namespaces/evaluator";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import { ScriptTarget } from "../../_namespaces/ts";
|
||||
|
||||
describe("unittests:: evaluation:: esDecoratorsMetadata", () => {
|
||||
const nodeVersion = new ts.Version(process.versions.node);
|
||||
const supportsClassStaticBlock = nodeVersion.major >= 16;
|
||||
|
||||
const targets = [
|
||||
// NOTE: Class static blocks weren't supported in Node v14
|
||||
...(supportsClassStaticBlock ? [ScriptTarget.ES2022] : []),
|
||||
ScriptTarget.ES2021,
|
||||
ScriptTarget.ES2015,
|
||||
];
|
||||
|
||||
for (const target of targets) {
|
||||
const targetName = ts.Debug.formatEnum(target, (ts as any).ScriptTarget);
|
||||
const options: ts.CompilerOptions = { target };
|
||||
const exec = (array: TemplateStringsArray) => evaluator.evaluateTypeScript(array[0], options);
|
||||
|
||||
describe("examples", () => {
|
||||
// see https://github.com/tc39/proposal-decorator-metadata
|
||||
it(`@meta (${targetName})`, () => {
|
||||
const { output } = exec`
|
||||
export const output: unknown[] = [];
|
||||
|
||||
function meta(key: string, value: string) {
|
||||
return (_, context) => {
|
||||
context.metadata[key] = value;
|
||||
};
|
||||
}
|
||||
|
||||
@meta('a', 'x')
|
||||
class C {
|
||||
@meta('b', 'y')
|
||||
m() { }
|
||||
}
|
||||
|
||||
output.push(C[Symbol.metadata].a);
|
||||
output.push(C[Symbol.metadata].b);
|
||||
`;
|
||||
assert.deepEqual(output, ["x", "y"]);
|
||||
});
|
||||
|
||||
// see https://github.com/tc39/proposal-decorator-metadata#inheritance
|
||||
it(`inheritance (${targetName})`, () => {
|
||||
const { output } = exec`
|
||||
export const output: unknown[] = [];
|
||||
|
||||
function meta(key: string, value: string) {
|
||||
return (_, context) => {
|
||||
context.metadata[key] = value;
|
||||
};
|
||||
}
|
||||
|
||||
@meta('a', 'x')
|
||||
class C {
|
||||
@meta('b', 'y')
|
||||
m() {}
|
||||
}
|
||||
|
||||
output.push(C[Symbol.metadata].a);
|
||||
output.push(C[Symbol.metadata].b);
|
||||
|
||||
class D extends C {
|
||||
@meta('b', 'z')
|
||||
m() {}
|
||||
}
|
||||
|
||||
output.push(D[Symbol.metadata].a);
|
||||
output.push(D[Symbol.metadata].b);
|
||||
`;
|
||||
assert.deepEqual(output, [
|
||||
"x",
|
||||
"y",
|
||||
"x",
|
||||
"z",
|
||||
]);
|
||||
});
|
||||
|
||||
// see https://github.com/tc39/proposal-decorator-metadata#inheritance
|
||||
it(`inheritance append(${targetName})`, () => {
|
||||
const { output } = exec`
|
||||
export const output: unknown[] = [];
|
||||
|
||||
function appendMeta(key: string, value: string) {
|
||||
return (_, context) => {
|
||||
const existing = context.metadata[key] ?? [];
|
||||
context.metadata[key] = [...existing, value];
|
||||
};
|
||||
}
|
||||
|
||||
@appendMeta('a', 'x')
|
||||
class C { }
|
||||
|
||||
@appendMeta('a', 'z')
|
||||
class D extends C { }
|
||||
|
||||
output.push(C[Symbol.metadata].a);
|
||||
output.push(D[Symbol.metadata].a);
|
||||
`;
|
||||
assert.deepEqual(output, [
|
||||
["x"],
|
||||
["x", "z"],
|
||||
]);
|
||||
});
|
||||
|
||||
// see https://github.com/tc39/proposal-decorator-metadata#private-metadata
|
||||
it(`private metadata (${targetName})`, () => {
|
||||
const { output } = exec`
|
||||
export const output: unknown[] = [];
|
||||
|
||||
const PRIVATE_METADATA = new WeakMap();
|
||||
function meta(key: string, value: string) {
|
||||
return (_, context) => {
|
||||
let metadata = PRIVATE_METADATA.get(context.metadata);
|
||||
if (!metadata) {
|
||||
metadata = {};
|
||||
PRIVATE_METADATA.set(context.metadata, metadata);
|
||||
}
|
||||
metadata[key] = value;
|
||||
};
|
||||
}
|
||||
|
||||
@meta('a', 'x')
|
||||
class C {
|
||||
@meta('b', 'y')
|
||||
m() { }
|
||||
}
|
||||
|
||||
output.push(PRIVATE_METADATA.get(C[Symbol.metadata]).a);
|
||||
output.push(PRIVATE_METADATA.get(C[Symbol.metadata]).b);
|
||||
`;
|
||||
assert.deepEqual(output, [
|
||||
"x",
|
||||
"y",
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user