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).