From 619e411b14e0bcbacf9a8d49c74f42ecd14bf842 Mon Sep 17 00:00:00 2001 From: Chris Hewell Garrett Date: Mon, 6 Mar 2023 21:50:58 -0500 Subject: [PATCH 1/2] Updates proposal to the latest design --- README.md | 167 ++++++++++++++++++++++++------------------------------ 1 file changed, 73 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index acfe02c..67096de 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,16 @@ recent version, however, as decorators only have access to the value they are _directly_ decorating (e.g. method decorators have access to the method, field decorators have access to the field, etc). -This proposal extends decorators by providing a value to use as a key to -associate metadata with. This key is then accessible via the -`Symbol.metadataKey` property on the class definition. +This proposal extends decorators by providing a metadata _object_, which can be +used either to directly store metadata, or as a WeakMap key. This object is +provided via the decorator's context argument, and is then accessible via the +`Symbol.metadata` property on the class definition after decoration. ## Detailed Design The overall decorator signature will be updated to the following: ```ts -interface MetadataKey { - parent: MetadataKey | null; -} - type Decorator = (value: Input, context: { kind: string; name: string | symbol; @@ -54,134 +51,116 @@ type Decorator = (value: Input, context: { isPrivate?: boolean; isStatic?: boolean; addInitializer?(initializer: () => void): void; -+ metadataKey?: MetadataKey; -+ class?: { -+ metadataKey: MetadataKey; -+ name: string; -+ } ++ metadata?: Record; }) => Output | void; ``` -Two new values are introduced, `metadataKey` and `class`. - -### `metadataKey` - -`metadataKey` is present for any _tangible_ decoratable value, specifically: - -- Classes -- Class methods -- Class accessors and auto-accessors +The new `metadata` property is a plain JavaScript object. The same object is +passed to every decorator applied to a class or any of its elements. After the +class has been fully defined, it is assigned to the `Symbol.metadata` property +of the class. -It is not present for class fields because they have no tangible value (e.g. -there is nothing to associate the metadata with, directly or indirectly). -`metadataKey` is then set on the decorated value once decoration has completed: +An example usage might look like: ```js -const METADATA = new WeakMap(); - -function meta(value) { +function meta(key, value) { return (_, context) => { - METADATA.set(context.metadataKey, value); + context.metadata[key] = value; }; } -@meta('a') +@meta('a' 'x') class C { - @meta('b') + @meta('b', 'y') m() {} } -METADATA.get(C[Symbol.metadata]); // 'a' -METADATA.get(C.m[Symbol.metadata]); // 'b' +C[Symbol.metadata].a; // 'x' +C[Symbol.metadata].b; // 'y' ``` -This allows metadata to be associated directly with the decorated value. - -### `class` - -The `class` object is available for all _class element_ decorators, including -fields. The `class` object contains two values: - -1. The `metadataKey` for the class itself -2. The name of the class +### Inheritance -This allows decorators for class elements to associate metadata with the class. -For method decorators, this can simplify certain flows. For class fields, since -they have no tangible value to associate metadata with, the class metadata key -is the only way to store their metadata. +If the decorated class has a parent class, then the prototype of the `metadata` +object is set to the metadata object of the superclass. This allows metadata to +be inherited in a natural way, taking advantage of shadowing by default, +mirroring class inheritance. For example: ```js -const METADATA = new WeakMap(); -const CLASS = Symbol(); - -function meta(value) { +function meta(key, value) { return (_, context) => { - const metadataKey = context.class?.metadataKey ?? context.metadataKey; - const metadataName = context.kind === 'class' ? CLASS : context.name; + context.metadata[key] = value; + }; +} - let meta = METADATA.get(metadataKey); +@meta('a' 'x') +class C { + @meta('b', 'y') + m() {} +} - if (meta === undefined) { - meta = new Map(); - METADATA.set(metadataKey, meta); - } +C[Symbol.metadata].a; // 'x' +C[Symbol.metadata].b; // 'y' - meta.set(metadataName, value); - }; +class D extends C { + @meta('b', 'z') + m() {} } -@meta('a') -class C { - @meta('b') - foo; +D[Symbol.metadata].a; // 'x' +D[Symbol.metadata].b; // 'z' +``` - @meta('c') - get bar() {} +In addition, metadata from the parent can be read during decoration, so it can +be modified or extended by children rather than overriding it. - @meta('d') - baz() {} +```ts +function appendMeta(key, value) { + return (_, context) => { + // NOTE: be sure to copy, not mutate + const existing = context.metadata[key] ?? []; + context.metadata[key] = [...existing, value]; + }; } -// Accessing the metadata -const meta = METADATA.get(C[Symbol.metadataKey]); +@appendMeta('a', 'x') +class C {} + +@appendMeta('a', 'z') +class D extends C {} -meta.get(CLASS); // 'a'; -meta.get('foo'); // 'b'; -meta.get('bar'); // 'c'; -meta.get('baz'); // 'd'; +C[Symbol.metadata].a; // ['x'] +D[Symbol.metadata].a; // ['x', 'z'] ``` -### `parent` +### Private Metadata -Metadata keys also have a `parent` property. This is set to the value of -`Symbol.metadataKey` on the prototype of the value being decorated. +In addition to public metadata placed directly on the metadata object, the +object can be used as a key in a `WeakMap` if the decorator author does not want +to share their metadata. -```js -const METADATA = new WeakMap(); +```ts +const PRIVATE_METADATA = new WeakMap(); -function meta(value) { +function meta(key, value) { return (_, context) => { - const classMetaKey = context.class.metadataKey; - const existingValue = METADATA.get(classMetaKey.parent) ?? 0; + let metadata = PRIVATE_METADATA.get(context.metadata); - METADATA.set(classMetaKey, existingValue + value); + if (!metadata) { + metadata = {}; + PRIVATE_METADATA.set(context.metadata, metadata); + } + + metadata[key] = value; }; } +@meta('a' 'x') class C { - @meta(1) - foo; -} - -class D extends C { - @meta(2) - foo; + @meta('b', 'y') + m() {} } -// Accessing the metadata -METADATA.get(C[Symbol.metadataKey]); // 3 +PRIVATE_METADATA.get(C[Symbol.metadata]).a; // 'x' +PRIVATE_METADATA.get(C[Symbol.metadata]).b; // 'y' ``` - -## Examples - -Todo From a193c0b54a9fce62901b4f0390d2da6c4e0bda96 Mon Sep 17 00:00:00 2001 From: Chris Hewell Garrett Date: Tue, 7 Mar 2023 14:48:54 -0500 Subject: [PATCH 2/2] Add spec text link --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 67096de..ac8c90b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ **Stage**: 2 +**Spec Text**: https://github.com/pzuraq/ecma262/pull/10 + This proposal seeks to extend the [Decorators](https://github.com/tc39/proposal-decorators) proposal by adding the ability for decorators to associate _metadata_ with the value being decorated.