• Stars
    star
    2,397
  • Rank 19,191 (Top 0.4 %)
  • Language
  • Created over 9 years ago
  • Updated about 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

This has moved to https://github.com/tc39/proposal-decorators

Summary

Decorators make it possible to annotate and modify classes and properties at design time.

While ES5 object literals support arbitrary expressions in the value position, ES6 classes only support literal functions as values. Decorators restore the ability to run code at design time, while maintaining a declarative syntax.

Detailed Design

A decorator is:

  • an expression
  • that evaluates to a function
  • that takes the target, name, and decorator descriptor as arguments
  • and optionally returns a decorator descriptor to install on the target object

Consider a simple class definition:

class Person {
  name() { return `${this.first} ${this.last}` }
}

Evaluating this class results in installing the name function onto Person.prototype, roughly like this:

Object.defineProperty(Person.prototype, 'name', {
  value: specifiedFunction,
  enumerable: false,
  configurable: true,
  writable: true
});

A decorator precedes the syntax that defines a property:

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

Now, before installing the descriptor onto Person.prototype, the engine first invokes the decorator:

let description = {
  type: 'method',
  initializer: () => specifiedFunction,
  enumerable: false,
  configurable: true,
  writable: true
};

description = readonly(Person.prototype, 'name', description) || description;
defineDecoratedProperty(Person.prototype, 'name', description);

function defineDecoratedProperty(target, { initializer, enumerable, configurable, writable }) {
  Object.defineProperty(target, { value: initializer(), enumerable, configurable, writable });
}

The has an opportunity to intercede before the relevant defineProperty actually occurs.

A decorator that precedes syntactic getters and/or setters operates on an accessor description:

class Person {
  @nonenumerable
  get kidCount() { return this.children.length; }
}

let description = {
  type: 'accessor',
  get: specifiedGetter,
  enumerable: true,
  configurable: true
}

function nonenumerable(target, name, description) {
  descriptor.enumerable = false;
  return descriptor;
}

A more detailed example illustrating a simple decorator that memoizes an accessor.

class Person {
  @memoize
  get name() { return `${this.first} ${this.last}` }
  set name(val) {
    let [first, last] = val.split(' ');
    this.first = first;
    this.last = last;
  }
}

let memoized = new WeakMap();
function memoize(target, name, descriptor) {
  let getter = descriptor.get, setter = descriptor.set;

  descriptor.get = function() {
    let table = memoizationFor(this);
    if (name in table) { return table[name]; }
    return table[name] = getter.call(this);
  }

  descriptor.set = function(val) {
    let table = memoizationFor(this);
    setter.call(this, val);
    table[name] = val;
  }
}

function memoizationFor(obj) {
  let table = memoized.get(obj);
  if (!table) { table = Object.create(null); memoized.set(obj, table); }
  return table;
}

It is also possible to decorate the class itself. In this case, the decorator takes the target constructor.

// A simple decorator
@annotation
class MyClass { }

function annotation(target) {
   // Add a property on target
   target.annotated = true;
}

Since decorators are expressions, decorators can take additional arguments and act like a factory.

@isTestable(true)
class MyClass { }

function isTestable(value) {
   return function decorator(target) {
      target.isTestable = value;
   }
}

The same technique could be used on property decorators:

class C {
  @enumerable(false)
  method() { }
}

function enumerable(value) {
  return function (target, key, descriptor) {
     descriptor.enumerable = value;
     return descriptor;
  }
}

Because descriptor decorators operate on targets, they also naturally work on static methods. The only difference is that the first argument to the decorator will be the class itself (the constructor) rather than the prototype, because that is the target of the original Object.defineProperty.

For the same reason, descriptor decorators work on object literals, and pass the object being created to the decorator.

Desugaring

Class Declaration

Syntax

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

Desugaring (ES6)

var Foo = (function () {
  class Foo {
  }

  Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
  return Foo;
})();

Desugaring (ES5)

var Foo = (function () {
  function Foo() {
  }

  Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
  return Foo;
})();

Class Method Declaration

Syntax

class Foo {
  @F("color")
  @G
  bar() { }
}

Desugaring (ES6)

var Foo = (function () {
  class Foo {
    bar() { }
  }

  var _temp;
  _temp = F("color")(Foo.prototype, "bar",
    _temp = G(Foo.prototype, "bar",
      _temp = Object.getOwnPropertyDescriptor(Foo.prototype, "bar")) || _temp) || _temp;
  if (_temp) Object.defineProperty(Foo.prototype, "bar", _temp);
  return Foo;
})();

Desugaring (ES5)

var Foo = (function () {
  function Foo() {
  }
  Foo.prototype.bar = function () { }

  var _temp;
  _temp = F("color")(Foo.prototype, "bar",
    _temp = G(Foo.prototype, "bar",
      _temp = Object.getOwnPropertyDescriptor(Foo.prototype, "bar")) || _temp) || _temp;
  if (_temp) Object.defineProperty(Foo.prototype, "bar", _temp);
  return Foo;
})();

Class Accessor Declaration

Syntax

class Foo {
  @F("color")
  @G
  get bar() { }
  set bar(value) { }
}

Desugaring (ES6)

var Foo = (function () {
  class Foo {
    get bar() { }
    set bar(value) { }
  }

  var _temp;
  _temp = F("color")(Foo.prototype, "bar",
    _temp = G(Foo.prototype, "bar",
      _temp = Object.getOwnPropertyDescriptor(Foo.prototype, "bar")) || _temp) || _temp;
  if (_temp) Object.defineProperty(Foo.prototype, "bar", _temp);
  return Foo;
})();

Desugaring (ES5)

var Foo = (function () {
  function Foo() {
  }
  Object.defineProperty(Foo.prototype, "bar", {
    get: function () { },
    set: function (value) { },
    enumerable: true, configurable: true
  });

  var _temp;
  _temp = F("color")(Foo.prototype, "bar",
    _temp = G(Foo.prototype, "bar",
      _temp = Object.getOwnPropertyDescriptor(Foo.prototype, "bar")) || _temp) || _temp;
  if (_temp) Object.defineProperty(Foo.prototype, "bar", _temp);
  return Foo;
})();

Object Literal Method Declaration

Syntax

var o = {
  @F("color")
  @G
  bar() { }
}

Desugaring (ES6)

var o = (function () {
  var _obj = {
    bar() { }
  }

  var _temp;
  _temp = F("color")(_obj, "bar",
    _temp = G(_obj, "bar",
      _temp = void 0) || _temp) || _temp;
  if (_temp) Object.defineProperty(_obj, "bar", _temp);
  return _obj;
})();

Desugaring (ES5)

var o = (function () {
    var _obj = {
        bar: function () { }
    }

    var _temp;
    _temp = F("color")(_obj, "bar",
        _temp = G(_obj, "bar",
            _temp = void 0) || _temp) || _temp;
    if (_temp) Object.defineProperty(_obj, "bar", _temp);
    return _obj;
})();

Object Literal Accessor Declaration

Syntax

var o = {
  @F("color")
  @G
  get bar() { }
  set bar(value) { }
}

Desugaring (ES6)

var o = (function () {
  var _obj = {
    get bar() { }
    set bar(value) { }
  }

  var _temp;
  _temp = F("color")(_obj, "bar",
    _temp = G(_obj, "bar",
      _temp = void 0) || _temp) || _temp;
  if (_temp) Object.defineProperty(_obj, "bar", _temp);
  return _obj;
})();

Desugaring (ES5)

var o = (function () {
  var _obj = {
  }
  Object.defineProperty(_obj, "bar", {
    get: function () { },
    set: function (value) { },
    enumerable: true, configurable: true
  });

  var _temp;
  _temp = F("color")(_obj, "bar",
    _temp = G(_obj, "bar",
      _temp = void 0) || _temp) || _temp;
  if (_temp) Object.defineProperty(_obj, "bar", _temp);
  return _obj;
})();

Grammar

  DecoratorList [Yield] :
   DecoratorList [?Yield]opt  Decorator [?Yield]

  Decorator [Yield] :
   @ LeftHandSideExpression [?Yield]

  PropertyDefinition [Yield] :
   IdentifierReference [?Yield]
   CoverInitializedName [?Yield]
   PropertyName [?Yield]  : AssignmentExpression [In, ?Yield]
   DecoratorList [?Yield]opt MethodDefinition [?Yield]

  CoverMemberExpressionSquareBracketsAndComputedPropertyName [Yield] :
   [ Expression [In, ?Yield] ]

NOTE The production CoverMemberExpressionSquareBracketsAndComputedPropertyName is used to cover parsing a MemberExpression that is part of a Decorator inside of an ObjectLiteral or ClassBody, to avoid lookahead when parsing a decorator against a ComputedPropertyName.

  PropertyName [Yield, GeneratorParameter] :
   LiteralPropertyName
   [+GeneratorParameter] CoverMemberExpressionSquareBracketsAndComputedPropertyName
   [~GeneratorParameter] CoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]

  MemberExpression [Yield]  :
   [Lexical goal InputElementRegExp] PrimaryExpression [?Yield]
   MemberExpression [?Yield] CoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]
   MemberExpression [?Yield] . IdentifierName
   MemberExpression [?Yield] TemplateLiteral [?Yield]
   SuperProperty [?Yield]
   NewSuper Arguments [?Yield]
   new MemberExpression [?Yield] Arguments [?Yield]

  SuperProperty [Yield] :
   super CoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]
   super . IdentifierName

  ClassDeclaration [Yield, Default] :
   DecoratorList [?Yield]opt class BindingIdentifier [?Yield] ClassTail [?Yield]
   [+Default] DecoratorList [?Yield]opt class ClassTail [?Yield]

  ClassExpression [Yield, GeneratorParameter] :
   DecoratorList [?Yield]opt class BindingIdentifier [?Yield]opt ClassTail [?Yield, ?GeneratorParameter]

  ClassElement [Yield] :
   DecoratorList [?Yield]opt MethodDefinition [?Yield]
   DecoratorList [?Yield]opt static MethodDefinition [?Yield]

Notes

In order to more directly support metadata-only decorators, a desired feature for static analysis, the TypeScript project has made it possible for its users to define ambient decorators that support a restricted syntax that can be properly analyzed without evaluation.

More Repositories

1

jquery-offline

A jQuery plugin to facilitate conveniently working with local storage
JavaScript
836
star
2

rack-offline

A Rack and Rails plugin for building offline web applications
Ruby
670
star
3

moneta

a unified interface to key/value stores
Ruby
476
star
4

merb-core

Merb Core: All you need. None you don't.
Ruby
437
star
5

bundler

Ruby
408
star
6

merb

master merb branch
Ruby
339
star
7

starbeam-historical

TypeScript
269
star
8

artifice

Replaces Net::HTTP with a subclass that routes all requests to a Rack application
Ruby
216
star
9

merb-plugins

Merb Plugins: Even more modules to hook up your Merb installation
JavaScript
208
star
10

merb-more

Merb More: The Full Stack. Take what you need; leave what you don't.
187
star
11

minispade

JavaScript
175
star
12

textmate

Command-line package manager for textmate
Ruby
160
star
13

rust-activesupport

Small demos that illustrate Rust's expressiveness
Rust
132
star
14

rake-pipeline-web-filters

Ruby
116
star
15

newgem-template

A basic template for building a brand new gem
Ruby
106
star
16

guides

Build your own Rails guides
JavaScript
92
star
17

javascript-private-state

A proposal for private state in JavaScript (Stage 0)
92
star
18

jspec

A JavaScript BDD Testing Library
JavaScript
83
star
19

mio-book

62
star
20

dbmonster

A demo of dbmon running on the idempotent-rerender branches of Ember and HTMLBars
JavaScript
61
star
21

handlebars-site

HTML
56
star
22

net-http

Ruby
43
star
23

rust-civet

A Rust web server
Rust
43
star
24

osx-window-sizing

AppleScripts to resize windows simply
42
star
25

js_module_transpiler

JS Module Transpiler is an experimental compiler that allows you to write your JavaScript using a subset of the current ES6 module syntax, and compile it into AMD modules (and soon, CommonJS modules)
Ruby
42
star
26

irb2

Ruby
37
star
27

hammer.rs

An option parsing library that deserializes flags into structs
Rust
37
star
28

asdf

Make the current directory available on port 9292
Ruby
35
star
29

parsejs

JavaScript
34
star
30

railsnatra

Ruby
32
star
31

benchwarmer

Prettier Benchmarking for Ruby
Ruby
32
star
32

muse

A library that can create HTML or PDF books from an extended Markdown format
Ruby
29
star
33

rust-bridge

Ruby
27
star
34

rust-arel

An in-progress port of the Ruby SQL building library arel
Rust
25
star
35

vigilo

A lightweight, simple API for watching the file system
Ruby
25
star
36

rails_assets

temporary home of Rails 3.1 assets until it's moved into master (in short order)
Ruby
24
star
37

dm-adapters

DataMapper Adapters
Ruby
22
star
38

ember-next-experiments

Shell
19
star
39

merb-extlib

Ruby core extensions library extracted from Merb core.
Ruby
19
star
40

experimental-amber-todos

The SproutCore todos tutorial using the single-file alpha Amber distribution
JavaScript
18
star
41

looper

Static analysis tools for ES6
JavaScript
16
star
42

rufo

a Ruby bridge to flying saucer
Ruby
16
star
43

ruby-spidermonkey

A Ruby Binding to Spidermonkey
C
15
star
44

shimmer

An experimental attempt to restructure Glimmer's concepts around a smaller set of primitives.
TypeScript
15
star
45

jquery-governance

Ruby
15
star
46

language-reporting

Rust
13
star
47

argon

Rust
13
star
48

chainable_compare

Ruby
12
star
49

net2-reactor

Ruby
12
star
50

hbs-parser-next

Messing around with a combinator-based approach to parser while stuck at home
TypeScript
12
star
51

prometheus

11
star
52

everafter

An experiment with making the Glimmer reactivity system more general
TypeScript
11
star
53

github-issues-demo

The code for the Github Issues demo I presented at EmberCamp UK
CSS
11
star
54

alexandria

Ruby bindings to the Google Data API
Ruby
11
star
55

rails-simple-benches

Some Rails benches that work across Rails versions and can be used to compare perf progress
Ruby
10
star
56

html5-parser

JavaScript
10
star
57

Unix.rb

C
10
star
58

sparta

Javascript running on Rubinius VM
Ruby
10
star
59

polaris-sketchwork

A collection of proposals that I (@wycats) am working on fleshing out for inclusion in Polaris.
JavaScript
10
star
60

rails-extension-tutorial

Ruby
9
star
61

rails-benches

Application benchmarks to confirm that optimization impact real-world performance
9
star
62

merb_dm_rest

A HTTP DataMapper bridge served via Merb
Ruby
9
star
63

syntax-highlighter

A web syntax highlighter to make it easy to write code to be pasted into keynote
JavaScript
9
star
64

nnhistory

HTML
9
star
65

rails2_on_rails3

Ruby
9
star
66

abbot-from-scratch

Ruby
9
star
67

jetty-rb

Ruby
9
star
68

allocation_counter

Ruby
8
star
69

active_params

8
star
70

software-patent-petition

8
star
71

w3c-dom

A W3C-compliant DOM written on top of libxml2 (very early stages)
Ruby
8
star
72

jsmodules

CSS
8
star
73

sproutcore-chrome-extension

A Chrome DevTools extension for SproutCore
JavaScript
8
star
74

indexeddb-experiment

This is an experiment building an IndexedDB adapter for Ember. It is very experimental and is tracking changes we are making in ember-data, so it is also unstable.
JavaScript
8
star
75

proc-macro-workshop

Rust
8
star
76

ember-future

Some experiments
JavaScript
7
star
77

rails-api

Ruby
7
star
78

cargo-website

CSS
7
star
79

jquery-ui-sproutcore20

JavaScript
7
star
80

modularity_olympics

My entry to Nick Kallen's modularity olympics (using the Middleware pattern as seen in Rack)
Ruby
7
star
81

simple-callbacks-refactor

Quick demo of how to make callbacks faster
Ruby
7
star
82

joy-ide

An IDE inspired by Rich Kilmer's Talk at Gogaruco
6
star
83

routed-http.rs

Combining rust-http with route-recognizer.rs to create routable HTTP
Rust
6
star
84

rake-pipeline-rails

Ruby
6
star
85

slicehub

An application for managing slices
6
star
86

wand

Rust
5
star
87

core-storage

TypeScript
5
star
88

merb-docs

Ruby
5
star
89

railsconf-rust-demo

Rust
5
star
90

sproutcore-interactive-tutorials

JavaScript
5
star
91

js_tabs_example

An example (not production code!) of how to write evented JS using tabs as an example
JavaScript
5
star
92

rdoc

rdoc fork to support arbitrary API level filtering
5
star
93

laszlo_post_api

Laszlo POST-API
Java
5
star
94

rust-experiments

Rust
5
star
95

rubygems-bug-demo

Ruby
5
star
96

handlebars-parser

An experimental rewrite of the Handlebars parser using Jison
JavaScript
5
star
97

at-media

The code from my @media talk
4
star
98

gkc

Rust
4
star
99

stalkr

Ruby
4
star
100

css_to_xpath

Extracted from Nokogiri in anticipation of merging into Merb
Ruby
4
star