Metadata Proposal - ECMAScript

Note
A shim for this API can be found here: https://github.com/rbuckton/ReflectDecorators

Proposal to add Metadata to ECMAScript.

1 Syntax

Note
This section is non-normative.
// define metadata on an object or property
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// check for presence of a metadata key on the prototype chain of an object or property
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// check for presence of an own metadata key of an object or property
let result = Reflect.hasOwnMetadata(metadataKey, target);
let result = Reflect.hasOwnMetadata(metadataKey, target, propertyKey);

// get metadata value of a metadata key on the prototype chain of an object or property
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

// get metadata value of an own metadata key of an object or property
let result = Reflect.getOwnMetadata(metadataKey, target);
let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);

// get all metadata keys on the prototype chain of an object or property
let result = Reflect.getMetadataKeys(target);
let result = Reflect.getMetadataKeys(target, propertyKey);

// get all own metadata keys of an object or property
let result = Reflect.getOwnMetadataKeys(target);
let result = Reflect.getOwnMetadataKeys(target, propertyKey);

// delete metadata from an object or property
let result = Reflect.deleteMetadata(metadataKey, target);
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);

// apply metadata via a decorator to a constructor
@Reflect.metadata(metadataKey, metadataValue)
class C {
  // apply metadata via a decorator to a method (property)
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}

// Design-time type annotations
function Type(type) { return Reflect.metadata("design:type", type); }
function ParamTypes(...types) { return Reflect.metadata("design:paramtypes", types); }
function ReturnType(type) { return Reflect.metadata("design:returntype", type); }

// Decorator application
@ParamTypes(String, Number)
class C {
  constructor(text, i) {
  }

  @Type(String)
  get name() { return "text"; }

  @Type(Function)
  @ParamTypes(Number, Number)
  @ReturnType(Number)
  add(x, y) {
    return x + y;
  }
}

// Metadata introspection
let obj = new C("a", 1);
let paramTypes = Reflect.getMetadata("design:paramtypes", obj, "add"); // [Number, Number]

2 Abstract Operations

2.1 Operations on Objects

2.1.1 GetOrCreateMetadataMap ( O, P, Create )

When the abstract operation GetOrCreateMetadataMap is called with Object O, property key P, and Boolean Create the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let targetMetadata be the value of O's [[Metadata]] internal slot.
  3. If targetMetadata is undefined , then
    1. If Create is false , return undefined .
    2. Set targetMetadata to be a newly created Map object.
    3. Set the [[Metadata]] internal slot of O to targetMetadata.
  4. Let metadataMap be ?  Invoke (targetMetadata, "get", P).
  5. If metadataMap is undefined , then
    1. If Create is false , return undefined .
    2. Set metadataMap to be a newly created Map object.
    3. Perform ?  Invoke (targetMetadata, "set", P, metadataMap).
  6. Return metadataMap.

3 Ordinary and Exotic Objects Behaviors

3.1 Ordinary Object Internal Methods and Internal Slots

All ordinary objects have an internal slot called [[Metadata]]. The value of this internal slot is either null or a Map object and is used for storing metadata for an object.

3.1.1 [[HasMetadata]] ( MetadataKey, P )

When the [[HasMetadata]] internal method of O is called with ECMAScript language value MetadataKey and property key P, the following steps are taken:

  1. Return ?  OrdinaryHasMetadata (MetadataKey, O, P).

3.1.1.1 OrdinaryHasMetadata ( MetadataKey, O, P )

When the abstract operation OrdinaryHasMetadata is called with ECMAScript language value MetadataKey, Object O, and property key P, the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let hasOwn be ?  OrdinaryHasOwnMetadata (MetadataKey, O, P).
  3. If hasOwn is true , return true .
  4. Let parent be ? O.[[GetPrototypeOf]]().
  5. If parent is not null , Return ? parent.[[HasMetadata]](MetadataKey, P).
  6. Return false .

3.1.2 [[HasOwnMetadata]] ( MetadataKey, P )

When the [[HasOwnMetadata]] internal method of O is called with ECMAScript language value MetadataKey and property key P, the following steps are taken:

  1. Return ?  OrdinaryHasOwnMetadata (MetadataKey, O, P).

3.1.2.1 OrdinaryHasOwnMetadata ( MetadataKey, O, P )

When the abstract operation OrdinaryHasOwnMetadata is called with ECMAScript language value MetadataKey, Object O, and property key P, the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let metadataMap be ?  GetOrCreateMetadataMap (O, P, false ).
  3. If metadataMap is undefined , return false .
  4. Return ?  ToBoolean (? Invoke (metadataMap, "has", MetadataKey)).

3.1.3 [[GetMetadata]] ( MetadataKey, P )

When the [[GetMatadata]] internal method of O is called with ECMAScript language value MetadataKey and property key P, the following steps are taken:

  1. Return ?  OrdinaryGetMetadata (MetadataKey, O, P).

3.1.3.1 OrdinaryGetMetadata ( MetadataKey, O, P )

When the abstract operation OrdinaryGetMetadata is called with ECMAScript language value MetadataKey, Object O, and property key P, the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let hasOwn be ?  OrdinaryHasOwnMetadata (MetadataKey, O, P).
  3. If hasOwn is true , return ?  OrdinaryGetOwnMetadata (MetadataKey, O, P).
  4. Let parent be ? O.[[GetPrototypeOf]]().
  5. If parent is not null , return ? parent.[[GetMetadata]](MetadataKey, P).
  6. Return undefined .

3.1.4 [[GetOwnMetadata]] ( MetadataKey, P, ParamIndex )

When the [[GetOwnMetadata]] internal method of O is called with ECMAScript language value MetadataKey and property key P, the following steps are taken:

  1. Return ?  OrdinaryGetOwnMetadata (MetadataKey, O, P).

3.1.4.1 OrdinaryGetOwnMetadata ( MetadataKey, O, P )

When the abstract operation OrdinaryGetOwnMetadata is called with ECMAScript language value MetadataKey, Object O, and property key P, the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let metadataMap be ?  GetOrCreateMetadataMap (O, P, false ).
  3. If metadataMap is undefined , return undefined .
  4. Return ?  Invoke (metadataMap, "get", MetadataKey).

3.1.5 [[DefineOwnMetadata]] ( MetadataKey, MetadataValue, P )

When the [[DefineOwnMetadata]] internal method of O is called with ECMAScript language value MetadataKey, ECMAScript language value MetadataValue, and property key P, the following steps are taken:

  1. Return ?  OrdinaryDefineOwnMetadata (MetadataKey, MetadataValue, O, P)

3.1.5.1 OrdinaryDefineOwnMetadata ( MetadataKey, MetadataValue, O, P )

When the abstract operation OrdinaryDefineOwnProperty is called with ECMAScript language value MetadataKey, ECMAScript language value MetadataValue, Object O, and property key P, the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let metadataMap be ?  GetOrCreateMetadataMap (O, P, true ).
  3. Return ?  Invoke (metadataMap, "set", MetadataKey, MetadataValue).

3.1.6 [[MetadataKeys]] ( P )

When the [[MetadataKeys]] internal method of O is called with property key P the following steps are taken:

  1. Return ?  OrdinaryMetadataKeys (O, P).

3.1.6.1 OrdinaryMetadataKeys ( O, P )

When the abstract operation OrdinaryMetadataKeys is called with Object O and property key P the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let ownKeys be ?  OrdinaryOwnMetadataKeys (O, P).
  3. Let parent be ? O.[[GetPrototypeOf]]().
  4. If parent is null , then return ownKeys.
  5. Let parentKeys be ? O.[[OrdinaryMetadataKeys]](P).
  6. Let ownKeysLen = ?  Get (ownKeys, "length").
  7. If ownKeysLen is 0 , return parentKeys.
  8. Let parentKeysLen = ?  Get (parentKeys, "length").
  9. If parentKeysLen is 0 , return ownKeys.
  10. Let set be a newly created Set object.
  11. Let keys be ?  ArrayCreate (0).
  12. Let k be 0 .
  13. For each element key of ownKeys
    1. Let hasKey be ?  Invoke (set, "has", key).
    2. If hasKey is false , then
      1. Let Pk be !  ToString (k).
      2. Perform ?  Invoke (set, "add", key).
      3. Let defineStatus be CreateDataProperty (keys, Pk, key).
      4. Assert : defineStatus is true .
      5. Increase k by 1 .
  14. For each element key of parentKeys
    1. Let hasKey be ?  Invoke (set, "has", key).
    2. If hasKey is false , then
      1. Let Pk be !  ToString (k).
      2. Perform ?  Invoke (set, "add", key).
      3. Let defineStatus be CreateDataProperty (keys, Pk, key).
      4. Assert : defineStatus is true .
      5. Increase k by 1 .
  15. Perform ?  Set (keys, "length", k).
  16. return keys.

3.1.7 [[OwnMetadataKeys]] ( P )

When the [[OwnMetadataKeys]] internal method of O is called with property key P the following steps are taken:

  1. Return OrdinaryOwnMetadataKeys (O, P).

3.1.7.1 OrdinaryOwnMetadataKeys ( O, P )

When the abstract operation OrdinaryOwnMetadataKeys is called with Object O and property key P the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let keys be ?  ArrayCreate (0).
  3. Let metadataMap be ?  GetOrCreateMetadataMap (O, P, false ).
  4. If metadataMap is undefined , return keys.
  5. Let keysObj be ?  Invoke (metadataMap, "keys").
  6. Let iterator be ?  GetIterator (keysObj).
  7. Let k be 0 .
  8. Repeat
    1. Let Pk be !  ToString (k).
    2. Let next be ?  IteratorStep (iterator).
    3. If next is false , then
      1. Let setStatus be ?  Set (keys, "length", k, true).
      2. Assert : setStatus is true .
      3. Return keys.
    4. Let nextValue be ?  IteratorValue (next).
    5. Let defineStatus be CreateDataPropertyOrThrow (keys, Pk, nextValue).
    6. If defineStatus is an abrupt completion , return ?  IteratorClose (iterator, defineStatus).
    7. Increase k by 1 .

3.1.8 [[DeleteMetadata]]( MetadataKey, P )

When the [[DeleteMetadata]] internal method of O is called with ECMAScript language value MetadataKey and property key P the following steps are taken:

  1. Assert : P is undefined or IsPropertyKey (P) is true .
  2. Let metadataMap be ?  GetOrCreateMetadataMap (O, P, false ).
  3. If metadataMap is undefined , return false .
  4. Return ?  Invoke (metadataMap, "delete", MetadataKey).

4 Reflection

4.1 The Reflect Object

This section contains amendments to the Reflect object.

4.1.1 Metadata Decorator Functions

A metadata decorator function is an anonymous built-in function that has [[MetadataKey]] and [[MetadataValue]] internal slots.

When a metadata decorator function F is called with arguments target and key, the following steps are taken:

  1. Assert : F has a [[MetadataKey]] internal slot whose value is an ECMAScript language value , or undefined .
  2. Assert : F has a [[MetadataValue]] internal slot whose value is an ECMAScript language value , or undefined .
  3. If Type (target) is not Object, throw a TypeError exception.
  4. If key is not undefined and IsPropertyKey (key) is false , throw a TypeError exception.
  5. Let metadataKey be the value of F's [[MetadataKey]] internal slot.
  6. Let metadataValue be the value of F's [[MetadataValue]] internal slot.
  7. Perform ? target.[[DefineMetadata]](metadataKey, metadataValue, target, key).
  8. Return undefined .

4.1.2 Reflect.metadata ( metadataKey, metadataValue )

When the metadata function is called with arguments metadataKey and metadataValue, the following steps are taken:

  1. Let decorator be a new built-in function object as defined in Metadata Decorator Functions.
  2. Set the [[MetadataKey]] internal slot of decorator to metadataKey.
  3. Set the [[MetadataValue]] internal slot of decorator to metadataValue.
  4. Return decorator.

4.1.3 Reflect.defineMetadata ( metadataKey, metadataValue, target [, propertyKey] )

When the defineMetadata function is called with arguments metadataKey, metadataValue, target, and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[DefineMetadata]](metadataKey, metadataValue, propertyKey).

4.1.4 Reflect.hasMetadata ( metadataKey, target [, propertyKey] )

When the hasMetadata function is called with arguments metadataKey, target, and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[HasMetadata]](metadataKey, propertyKey).

4.1.5 Reflect.hasOwnMetadata ( metadataKey, target [, propertyKey] )

When the hasOwnMetadata function is called with arguments metadataKey, target, and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[HasOwn]](metadataKey, propertyKey).

4.1.6 Reflect.getMetadata ( metadataKey, target [, propertyKey] )

When the getMetadata function is called with arguments metadataKey, target, and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[GetMetadata]](metadataKey, propertyKey).

4.1.7 Reflect.getOwnMetadata ( metadataKey, target [, propertyKey] )

When the getOwnMetadata function is called with arguments metadataKey, target, and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[GetOwnMetadata]](metadataKey, propertyKey).

4.1.8 Reflect.getMetadataKeys ( target [, propertyKey] )

When the getMetadataKeys function is called with arguments target and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[GetMetadataKeys]](propertyKey).

4.1.9 Reflect.getOwnMetadataKeys ( target [, propertyKey] )

When the getOwnMetadataKeys function is called with arguments target and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[GetOwnMetadataKeys]](propertyKey).

4.1.10 Reflect.deleteMetadata ( metadataKey, target [, propertyKey] )

When the deleteMetadata function is called with arguments metadataKey, target, and propertyKey, the following steps are taken:

  1. If Type (target) is not Object, throw a TypeError exception.
  2. Return ? target.[[DeleteMetadata]](metadataKey, propertyKey).