Decorators Proposal - TypeScript

Table of Contents

  1. Introduction
  2. 1 Terms
    1. 1.1 Decorator
    2. 1.2 Class Decorator Function
    3. 1.3 Property/Method Decorator Function
    4. 1.4 Parameter Decorator Function
    5. 1.5 Decorator Factory
  3. 2 Decorator Targets
  4. 3 Decorator Evaluation and Application Order
  5. 4 Reflect API
  6. 5 Transformation
    1. 5.1 Class Declaration
    2. 5.2 Class Declaration (Exported)
    3. 5.3 Class Declaration (Default, Exported)
    4. 5.4 Class Method Declaration
    5. 5.5 Class Accessor Declaration
    6. 5.6 Class Property Declaration
    7. 5.7 Class Constructor Parameter Declaration
    8. 5.8 Class Method Parameter Declaration
    9. 5.9 Class Set Accessor Parameter Declaration
  7. A Grammar
    1. A.1 Expressions
    2. A.2 Functions and Classes
    3. A.3 Scripts and Modules
  8. B TypeScript
    1. B.1 TypeScript Definitions

Introduction

Proposal to add Decorators to TypeScript.

For the ECMAScript specific proposal, see http://rbuckton.github.io/reflectdecorators/index.html

1Terms

1.1Decorator

NoteThis 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 {
}

1.2Class Decorator Function

NoteThis 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...
}

1.3Property/Method Decorator Function

NoteThis 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...
}
    

1.4Parameter Decorator Function

NoteThis section is non-normative.

A parameter decorator function is a function that accepts three arguments: The function that contains the decorated parameter, the property key of the member (or undefined for a parameter of the constructor), and the ordinal index of the parameter. The return value of this decorator is ignored.


// A parameter decorator
function dec(target, paramIndex) {
    // annotate the target and index
}

1.5Decorator Factory

NoteThis 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...
  }
}

2Decorator Targets

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

3Decorator Evaluation and Application Order

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

4Reflect API

NoteThis section is non-normative.

In addition to a declarative approach to defining decorators, it is necessary to also include an imperative API capable of applying decorators, as well as defining, reflecting over, and removing decorator metadata from an object, property, or parameter.

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

Reflect.decorate(decorators, target, propertyKey?, descriptor?)

5Transformation

The following are examples of how decorators can be desugared to ES6 (through a transpiler such as TypeScript). These examples levarage an imperative reflection API.

5.1Class Declaration

Syntax

@F("color")
@G
class C {  
}
    

ES6 Transformation

let C = class {
}
Object.defineProperty(C, "name", { value: "C", configurable: true });
C = __decorate([F("color"), G], C);
    

5.2Class Declaration (Exported)

Syntax

@F("color")
@G
export class C {
}
    

ES6 Transformation

export let C = class {
}
Object.defineProperty(C, "name", { value: "C", configurable: true });
C = __decorate([F("color"), G], C);
    

5.3Class Declaration (Default, Exported)

Syntax

@F("color")
@G
export default class C {
}
    

ES6 Transformation

let C = class {
}
Object.defineProperty(C, "name", { value: "C", configurable: true });
C = __decorate([F("color"), G], C);
export default C;
    

5.4Class Method Declaration

Syntax

class C {
    @F("color")
    @G
    method() { }
}
    

ES6 Transformation

class C {
    method() { }
}
Object.defineProperty(C.prototype, "method", 
    __decorate([F("color"), G], C.prototype, "method", Object.getOwnPropertyDescriptor(C.prototype, "method")));
    

5.5Class Accessor Declaration

Syntax

class C {
    @F("color")
    @G
    get accessor() { }
    set accessor(value) { }
}
    

ES6 Transformation

class C {
    get accessor() { }
    set accessor(value) { }
}
Object.defineProperty(C.prototype, "accessor", 
    __decorate([F("color"), G], C.prototype, "accessor", Object.getOwnPropertyDescriptor(C.prototype, "accessor")));
    

5.6Class Property Declaration

Syntax

class C {
    @F("color")
    @G
    property = 1;
}
    

ES6 Transformation

class C {
    constructor() {
        this.property = 1;
    }
}
__decorate([F("color"), G], C.prototype, "property");
    

5.7Class Constructor Parameter Declaration

Syntax

class C {
    constructor(@F("color") @G p) { }
}
    

ES6 Transformation

class C {
    constructor(p) { }
}
__decorate([F("color"), G], C, void 0, 0);
    

5.8Class Method Parameter Declaration

Syntax

class C {
    method(@F("color") @G p) { }
}
    

ES6 Transformation

class C {
    method(p) { }
}
__decorate([F("color"), G], C.prototype, "method", 0);
    

5.9Class Set Accessor Parameter Declaration

Syntax

class C {
    set accessor(@F("color") @G p) { }
}
    

ES6 Transformation

class C {
    set accessor(p) { }
}
__decorate([F("color"), G], C.prototype, "accessor", 0);
    

AGrammar

A.1Expressions

MemberExpression [Yield, Decorator]:PrimaryExpression [?Yield] [~Decorator]MemberExpression [?Yield, ?Decorator][Expression [In, ?Yield]] MemberExpression [?Yield, ?Decorator].IdentifierName MemberExpression [?Yield, ?Decorator]TemplateLiteral [?Yield] SuperProperty [?Yield, ?Decorator] MetaProperty newMemberExpression [?Yield, ?Decorator]Arguments [?Yield] SuperProperty [Yield, Decorator]:[~Decorator]super[Expression [In, ?Yield]] NewExpression [Yield, Decorator]:MemberExpression [?Yield, ?Decorator] newNewExpression [?Yield, ?Decorator] CallExpression [Yield, Decorator]:MemberExpression [?Yield, ?Decorator]Arguments [?Yield] SuperCall [?Yield] CallExpression [?Yield, ?Decorator]Arguments [?Yield] [~Decorator]CallExpression [?Yield][Expression [In, ?Yield]] CallExpression [?Yield, ?Decorator].IdentifierName CallExpression [?Yield, ?Decorator]TemplateLiteral [?Yield] LeftHandSideExpression [Yield, Decorator]:NewExpression [?Yield, ?Decorator] CallExpression [?Yield, ?Decorator]

A.2Functions and Classes

StrictFormalParameters [Yield, GeneratorParameter, ClassParameter]:FormalParameters [?Yield, ?GeneratorParameter, ?ClassParameter] FormalParameters [Yield, GeneratorParameter, ClassParameter]:[empty] FormalParameterList [?Yield, ?GeneratorParameter, ?ClassParameter] FormalParameterList [Yield, GeneratorParameter, ClassParameter]:FunctionRestParameter [?Yield, ?ClassParameter] FormalsList [?Yield, ?GeneratorParameter, ?ClassParameter] FormalsList [?Yield, ?GeneratorParameter, ?ClassParameter],FunctionRestParameter [?Yield, ?ClassParameter] FormalsList [Yield, GeneratorParameter, ClassParameter]:FormalParameter [?Yield, ?GeneratorParameter, ?ClassParameter] FormalsList [?Yield, ?GeneratorParameter, ?ClassParameter],FormalParameter [?Yield, ?GeneratorParameter, ?ClassParameter] FunctionRestParameter [Yield, ClassParameter]:BindingRestElement [?Yield] [+ClassParameter]DecoratorListBindingRestElement [?Yield] FormalParameter [Yield, GeneratorParameter, ClassParameter]:BindingElement [?Yield, ?GeneratorParameter] [+ClassParameter]DecoratorListBindingElement [?Yield, ?GeneratorParameter] MethodDefinition [Yield, ClassParameter]:PropertyName [?Yield](StrictFormalParameters [?ClassParameter]){FunctionBody} GeneratorMethod [?Yield, ?ClassParameter] getPropertyName [?Yield](){FunctionBody} setPropertyName [?Yield](PropertySetParameterList [?ClassParameter]){FunctionBody} GeneratorMethod [Yield, ClassParameter]:*PropertyName [?Yield](StrictFormalParameters [Yield, GeneratorParameter, ?ClassParameter]){GeneratorBody} ClassDeclaration [Yield, Default]:DecoratorList [?Yield] optclassBindingIdentifier [?Yield]ClassTail [?Yield] [+Default]DecoratorList [?Yield] optclassClassTail [?Yield] ClassExpression [Yield, GeneratorParameter]:DecoratorList [?Yield] optclassBindingIdentifier [?Yield] optClassTail [?Yield, ?GeneratorParameter] ClassElement [Yield]:DecoratorList [?Yield] optMethodDefinition [ClassParameter, ?Yield] DecoratorList [?Yield] optstaticMethodDefinition [ClassParameter, ?Yield] DecoratorList [?Yield] optstaticPropertyNameInitializer [In, ?Yield] opt; ; DecoratorList [Yield]:DecoratorList [?Yield] optDecorator [?Yield] Decorator [Yield]:@LeftHandSideExpression [Decorator, ?Yield]

A.3Scripts and Modules

ExportDeclaration:export*FromClause; exportExportClauseFromClause; exportExportClause; exportVariableStatement export[lookahead ≠ @]Declaration exportdefaultHoistableDeclaration [Default] exportdefault[lookahead ≠ @]ClassDeclaration [Default] exportdefault[lookahead ∉ { function, class, @ }]AssignmentExpression DecoratorListexport[lookahead ≠ @]ClassDeclaration DecoratorListexportdefault[lookahead ≠ @]ClassDeclaration [Default]

BTypeScript

B.1TypeScript Definitions

interface TypedPropertyDescriptor<T> {  
    enumerable?: boolean;  
    configurable?: boolean;  
    writable?: boolean;  
    value?: T;  
    get?: () => T;  
    set?: (value: T) => void;  
}  

type ClassDecorator = <TFunction extends Function>(target: TFunction): TFunction | void;
type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void;
type PropertyDecorator = (target: Object, propertyKey: string | symbol): void;
type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number): void;