Decorators Proposal - ECMAScript

Introduction

Proposal to add Decorators to ECMAScript.

For the TypeScript specific proposal, see http://rbuckton.github.io/reflectdecorators/typescript.html

Terms

Decorator

This section is non-normative.

A decorator is an expression that is evaluated after a class has been defined, that can be used to annotate or modify the class in some fashion. This expression must evaluate to a function, which is executed by the runtime to apply the decoration.

@decoratorExpression
class C {
}

Class Decorator Function

This section is non-normative.

A class decorator function is a function that accepts a constructor function as its argument, and returns either undefined, the provided constructor function, or a new constructor function. Returning undefined is equivalent to returning the provided constructor function.

// A class decorator function
function dec(target) {  
 // modify, annotate, or replace target...
}

Property/Method Decorator Function

This section is non-normative.

A property decorator function is a function that accepts three arguments: The object that owns the property, the key for the property (a string or a symbol), and optionally the property descriptor of the property. The function must return either undefined, the provided property descriptor, or a new property descriptor. Returning undefined is equivalent to returning the provided property descriptor.

// A property (or method/accessor) decorator function
function dec(target, key, descriptor) {
  // annotate the target and key; or modify or replace the descriptor...
}
    

Decorator Factory

This section is non-normative.

A decorator factory is a function that can accept any number of arguments, and must return one of the above types of decorator function.

// a class decorator factory function
function dec(x, y) {
  // the class decorator function
  return function (target) {
      // modify, annotate, or replace target...
  }
}

Decorator Targets

This section is non-normative.

A decorator can be legally applied to any of the following:

Please note that a decorator currently cannot be legally applied to any of the following:

This list may change in the future.

Decorator Evaluation and Application Order

This section is non-normative.

Decorators are evaluated in the order they appear preceeding their target declaration, to preserve side-effects due to evaluation order. Decorators are applied to their target declaration in reverse order, starting with the decorator closest to the declaration. This behavior is specified to preserve the expected behavior of decorators without a declarative syntax.

@F
@G
class C {   
}

For example, the above listing could be approximately written without decorators in the following fashion:

C = F(G(C))

In the above example, the expression F is evaluated first, followed by the expression G. G is then called with the constructor function as its argument, followed by calling F with the result. The actual process of applying decorators is more complex than the above example however, though you may still imperatively apply decorators with a reflection API.

If a class declaration has decorators on both the class and any of its members or parameters, the decorators are applied using the following pseudocode:

for each member M of class C
  if M is an accessor then
      let accessor = first accessor (get or set, in declaration order) of M
      let memberDecorators = decorators of accessor
      for each parameter of accessor
          let paramDecorators = decorators of parameter           
          let paramIndex = ordinal index of parameter
          Reflect.decorate(paramDecorators, accessor, paramIndex)
      next parameter

      let accessor = second accessor (get or set, in declaration order) of M
      if accessor then
          let memberDecorators = memberDecorators + decorators of accessor
          for each parameter of accessor
              let paramDecorators = decorators of parameter           
              let paramIndex = ordinal index of parameter
              Reflect.decorate(paramDecorators, accessor, paramIndex)
          next parameter
      end if
  else if M is a method
      let memberDecorators = decorators of M
      for each parameter of M
          let paramDecorators = decorators of parameter           
          let paramIndex = ordinal index of parameter
          Reflect.decorate(paramDecorators, M, paramIndex)
      next parameter
  else
      let memberDecorators = decorators of M
  end if

  let name = name of M
  let target = C.prototype if M is on the prototype; otherwise, C if M is static  
  Reflect.decorate(memberDecorators, C, name)
next member

for each parameter of C
  let paramDecorators = decorators of parameter
  let paramIndex = ordinal index of parameter
  Reflect.decorate(paramDecorators, C, paramIndex)
next parameter

let classDecorators = decorators of C
let C = Reflect.decorate(classDecorators, C)
  

Abstract Operations

Decorator Operations

Decorate ( Decorators, O, P, desc )

When the abstract operation Decorate is called with ECMAScript language value Decorators, Object O, property key P, and property descriptor desc, the following steps are taken:

1. If _P_ is *undefined* and _desc_ is *undefined*, then 1. If IsCallable(_O_) is not *true*, throw a *TypeError* exception. 2. Return DecorateConstructor(_Decorators_, _O_). 2. Else 1. Return DecorateProperty(_Decorators_, _O_, _P_, _desc_).

DecorateConstructor ( Decorators, F )

When the abstract operation DecorateConstructor is called with ECMAScript language value Decorators and Object F, the following steps are taken:

1. Let _result_ be _F_. 2. Let _iterator_ be GetIterator(_Decorators_). 3. Let _list_ be CreateListFromIterator(_iterator_, «Object»). 4. For each _decorator_ in _list_ in reverse order 1. Let _decorated_ be Call(_decorator_, *null*, _result_). 2. ReturnIfAbrupt(_decorated_). 3. If IsCallable(_decorated_), then 1. Set _result_ to be _decorated_. 4. Else if _decorated_ is not *undefined*, throw a *TypeError* exception.

DecorateProperty ( Decorators, O, P, desc )

When the abstract operation DecorateProperty is called with ECMAScript language value Decorators, Object O, property key P, and property descriptor desc, the following steps are taken:

1. Let _result_ be _desc_. 2. Let _key_ be ToPropertyKey(_P_). 3. Let _iterator_ be GetIterator(_Decorators_). 4. Let _list_ be CreateListFromIterator(_iterator_, «Object»). 5. For each _decorator_ in _list_ in reverse order 6. Let _decorated_ be Call(_decorator_, *null*, _O_, _key_, _result_). 7. ReturnIfAbrupt(_decorated_). 8. If Type(_decorated_) is Object, then 1. Set _result_ to be _decorated_. 9. Else if _decorated_ is not *undefined*, throw a *TypeError* exception.

Operations on Objects

CreateListFromIterator ( iterator [, elementTypes] )

When the abstract operation CreateListFromIterator is called with ECMAScript language value iterator, the following steps are taken:

1. ReturnIfAbrupt(_iterator_). 2. If _elementTypes_ was not passed, let _elementTypes_ be (Undefined, Null, Boolean, String, Symbol, Number, Object). 3. Let _list_ be an empty List. 4. Repeat 1. Let _next_ be IteratorStep(_iterator_). 2. ReturnIfAbrupt(_next_). 3. If _next_ is *false*, return _list_. 4. Let _nextValue_ be IteratorValue(_next_). 5. ReturnIfAbrupt(_nextValue_). 6. If Type(_nextValue_) is not an element of _elementTypes_, then 1. Return IteratorClose(_iterator_, Completion{[[type]]: throw, [[value]]: a newly created *TypeError* object, [[target]: empty}). 7. Append _nextValue_ as the last element of _list_.

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 _metadataMap_ be *undefined*. 3. Let _succeeded_ be *true*. 4. Let _targetMetadata_ be the value of _O_'s [[Metadata]] internal slot. 5. 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_. 6. Let _metadataMap_ be Invoke(_targetMetadata_, "get", _P_). 7. ReturnIfAbrupt(_metadataMap_). 8. If _metadataMap_ is *undefined*, then 1. If _Create_ is *false*, return *undefined*. 2. Set _metadataMap_ to be a newly created *Map* object. 3. Let _setStatus_ be Invoke(_targetMetadata_, "set", _P_, _metadataMap_). 4. ReturnIfAbrupt(_setStatus_). 9. Return _metadataMap_.

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

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. ReturnIfAbrupt(_parent_). 6. If _parent_ is not *null*, then 1. return parent.[[HasMetadata]](_MetadataKey_, _P_). 7. Return *false*.

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

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. ReturnIfAbrupt(_metadataMap_). 4. If _metadataMap_ is *undefined*, return *false*. 5. Return Invoke(_metadataMap_, "has", _MetadataKey_).

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

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*, then 1. return OrdinaryGetOwnMetadata(_MetadataKey_, _O_, _P_). 4. Let _parent_ be _O_.[[GetPrototypeOf]](). 5. ReturnIfAbrupt(_parent_). 6. If _parent_ is not *null*, then 1. return parent.[[GetMetadata]](_MetadataKey_, _P_). 7. Return *undefined*.

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

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. ReturnIfAbrupt(_metadataMap_). 4. If _metadataMap_ is *undefined*, return *undefined*. 5. Return Invoke(_metadataMap_, "get", _MetadataKey_).

[[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_)

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. ReturnIfAbrupt(_metadataMap_). 4. Return Invoke(_metadataMap_, "set", _MetadataKey_, _MetadataValue_).

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

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_ = _O_.[[GetPrototypeOf]](). 4. ReturnIfAbrupt(_parent_). 5. If _parent_ is *null*, then return _ownKeys_. 6. Let _parentKeys_ be O.[[OrdinaryMetadataKeys]](_P_). 7. ReturnIfAbrupt(_parentKeys_). 8. Let _ownKeysLen_ = Get(_ownKeys_, "length"). 9. ReturnIfAbrupt(_ownKeysLen_). 10. If _ownKeysLen_ is 0, return _parentKeys_. 11. Let _parentKeysLen_ = Get(_parentKeys_, "length"). 12. ReturnIfAbrupt(_parentKeysLen_). 13. If _parentKeysLen_ is 0, return _ownKeys_. 14. Let _set_ be a newly created *Set* object. 15. Let _keys_ be ArrayCreate(0). 16. Let _k_ be 0. 17. For each element _key_ of _ownKeys_ 1. Let _hasKey_ be Invoke(_set_, "has", _key_). 2. ReturnIfAbrupt(_hasKey_). 3. If _hasKey_ is *false*, then 1. Let _Pk_ be ToString(_k_). 2. Let _addStatus_ be Invoke(_set_, "add", _key_). 3. ReturnIfAbrupt(_addStatus_). 4. Let _defineStatus_ be CreateDataPropertyOrThrow(_keys_, _Pk_, _key_). 5. ReturnIfAbrupt(_defineStatus_). 6. Increase _k_ by 1. 18. For each element _key_ of _parentKeys_ 1. Let _hasKey_ be Invoke(_set_, "has", _key_). 2. ReturnIfAbrupt(_hasKey_). 3. If _hasKey_ is *false*, then 1. Let _Pk_ be ToString(_k_). 2. Let _addStatus_ be Invoke(_set_, "add", _key_). 3. ReturnIfAbrupt(_addStatus_). 4. Let _defineStatus_ be CreateDataPropertyOrThrow(_keys_, _Pk_, _key_). 5. ReturnIfAbrupt(_defineStatus_). 6. Increase _k_ by 1. 19. Let _setStatus_ be Set(_keys_, "length", _k_). 20. ReturnIfAbrupt(_setStatus_). 21. return _keys_.

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

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. ReturnIfAbrupt(_metadataMap_). 5. If _metadataMap_ is *undefined*, return _keys_. 6. Let _keysObj_ be Invoke(_metadataMap_, "keys"). 7. ReturnIfAbrupt(_keysObj_). 8. Let _iterator_ be GetIterator(_keysObj_). 9. ReturnIfAbrupt(_iterator_). 10. Let _k_ be 0. 11. Repeat 1. Let _Pk_ be ToString(k). 2. Let _next_ be IteratorStep(_iterator_). 3. ReturnIfAbrupt(_next_). 4. If _next_ is *false*, then 1. Let _setStatus_ be Set(_keys_, "length", _k_, _true_). 2. ReturnIfAbrupt(_setStatus_). 3. Return _keys_. 5. Let _nextValue_ be IteratorValue(_next_). 6. ReturnIfAbrupt(_nextValue_). 7. Let _defineStatus_ be CreateDataPropertyOrThrow(_keys_, _Pk_, _nextValue_). 8. If _defineStatus_ is an abrupt completion, return IteratorClose(_iterator_, _defineStatus_). 9. Increase _k_ by 1.

[[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. ReturnIfAbrupt(_metadataMap_). 4. If _metadataMap_ is *undefined*, return *false*. 5. Return Invoke(_metadataMap_, "delete", _MetadataKey_).

Reflection

The Reflect Object

This section contains amendments to the Reflect object.

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

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. Return target.[[DefineMetadata]](_metadataKey_, _metadataValue_, _target_, _key_).

Reflect.decorate ( decorators, target, propertyKey, attributes )

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

1. If Type(_decorators_) is not Object, throw a *TypeError* exception. 2. If Type(_target_) is not Object, throw a *TypeError* exception. 3. Let _key_ be ToPropertyKey(_propertyKey_). 4. ReturnIfAbrupt(_propertyKey_). 5. Let _desc_ be ToPropertyDescriptor(_attributes_). 6. ReturnIfAbrupt(_desc_). 7. Return Decorate(_decorators_, _target_, _propertyKey_, _desc_).

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

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

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

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

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

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

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

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

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

Grammar

Expressions

PrimaryExpression MemberExpression[Expression] MemberExpression.IdentifierName MemberExpressionTemplateLiteral SuperProperty MetaProperty newMemberExpressionArguments super[Expression] MemberExpression newNewExpression MemberExpression Arguments SuperCall CallExpression Arguments CallExpression [ Expression ] CallExpression . IdentifierName CallExpression TemplateLiteral NewExpression CallExpression

Functions and Classes

DecoratorList class BindingIdentifier ClassTail DecoratorList class ClassTail DecoratorList class BindingIdentifier ClassTail DecoratorList MethodDefinition DecoratorList static MethodDefinition ; DecoratorList Decorator @ LeftHandSideExpression

Scripts and Modules

export * FromClause ; export ExportClause FromClause ; export ExportClause ; export VariableStatement export lookahead ≠ @ Declaration export default HoistableDeclaration export default lookahead ≠ @ ClassDeclaration export default lookahead ∉ { function, class, @ } AssignmentExpression DecoratorList export lookahead ≠ @ ClassDeclaration DecoratorList export default lookahead ≠ @ ClassDeclaration