├── test ├── mocha.opts ├── .eslintrc └── decorators.test.js ├── .npmrc ├── .eslintrc ├── .gitignore ├── .editorconfig ├── examples ├── method-decorators │ ├── class-props.js │ ├── double-decorate-get-set.js │ ├── preserve-ctor.js │ ├── class-method-rename.js │ ├── combine-getter-setter.js │ ├── obj-prop.js │ ├── finishers.js │ ├── obj-spread.js │ ├── symbol-key.js │ ├── extras.js │ ├── reflect-metadata.js │ └── coalesce.js └── class-decorators │ ├── mark-constructor.js │ └── wrap-constructor.js ├── .travis.yml ├── README.md ├── LICENSE ├── package.json ├── CONTRIBUTING.md └── lib └── babel-plugin-transform-decorators-stage-2-initial.js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "groupon-es5" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | /tmp 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "func-names": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /examples/method-decorators/class-props.js: -------------------------------------------------------------------------------- 1 | /* @transform class-properties */ 2 | function id(a) { 3 | return a; 4 | } 5 | 6 | class X { 7 | x = 2; 8 | 9 | @id f() { return this.x; } 10 | } 11 | 12 | assert.equal(2, new X().f()); 13 | -------------------------------------------------------------------------------- /examples/method-decorators/double-decorate-get-set.js: -------------------------------------------------------------------------------- 1 | function d(e) { return e; } 2 | 3 | var err = assert.throws(function () { 4 | class X { 5 | @d get a() {} 6 | @d set a(n) {} 7 | } 8 | }); 9 | 10 | assert.equal( 11 | 'Cannot decorate both getter and setter of the same property', 12 | err.message); 13 | -------------------------------------------------------------------------------- /examples/class-decorators/mark-constructor.js: -------------------------------------------------------------------------------- 1 | var meta = new Map(); 2 | 3 | function mark(klass) { 4 | klass.finisher = ctor => { 5 | meta.set(ctor, 'marked'); 6 | }; 7 | return klass; 8 | } 9 | 10 | @mark class X { g() { return 42; } } 11 | 12 | assert.equal('marked', meta.get(X)); 13 | assert.equal(42, new X().g()); 14 | -------------------------------------------------------------------------------- /examples/method-decorators/preserve-ctor.js: -------------------------------------------------------------------------------- 1 | const id = a => a; 2 | 3 | class C { 4 | constructor() { 5 | this._x = 'foo'; 6 | } 7 | 8 | @id 9 | get x() { return this._x; } 10 | } 11 | 12 | assert.equal(new C().x, 'foo'); 13 | assert.include('Preserves constructor body when possible', 14 | 'this._x =', C.toString()); 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | - '6' 5 | before_deploy: 6 | - git config --global user.email "jan.krems@gmail.com" 7 | - git config --global user.name "Jan Krems" 8 | deploy: 9 | provider: script 10 | script: ./node_modules/.bin/nlm release 11 | skip_cleanup: true 12 | 'on': 13 | branch: master 14 | node: '6' 15 | -------------------------------------------------------------------------------- /examples/method-decorators/class-method-rename.js: -------------------------------------------------------------------------------- 1 | function rename(newName) { 2 | return function decorate(element) { 3 | element.key = newName; 4 | return element; 5 | }; 6 | } 7 | 8 | class X { 9 | @rename('b') a() { return 7; } 10 | @rename('y') static x() { return 42; } 11 | } 12 | 13 | assert.equal(7, new X().b()); 14 | assert.equal(42, X.y()); 15 | -------------------------------------------------------------------------------- /examples/method-decorators/combine-getter-setter.js: -------------------------------------------------------------------------------- 1 | var callCount = 0; 2 | function decorate(element) { 3 | ++callCount; 4 | assert.hasType(Function, element.descriptor.get); 5 | assert.hasType(Function, element.descriptor.set); 6 | return element; 7 | } 8 | 9 | class X { 10 | @decorate 11 | get a() { return 42; } 12 | set a(v) {} 13 | } 14 | 15 | assert.equal(1, callCount); 16 | -------------------------------------------------------------------------------- /examples/method-decorators/obj-prop.js: -------------------------------------------------------------------------------- 1 | function d(e) { 2 | return e; 3 | } 4 | 5 | var obj = { 6 | a: 10, 7 | @d f() { return 42; }, 8 | b: 20 9 | }; 10 | assert.hasType(Function, obj.f); 11 | assert.equal(42, obj.f()); 12 | assert.equal(10, obj.a); 13 | assert.equal(20, obj.b); 14 | // We should not be changing the order of properties 15 | assert.deepEqual(['a', 'f', 'b'], Object.keys(obj)); 16 | -------------------------------------------------------------------------------- /examples/method-decorators/finishers.js: -------------------------------------------------------------------------------- 1 | var finishCalled = []; 2 | var finishArgs = []; 3 | 4 | function finish(element) { 5 | element.finisher = function runFinisher(target) { 6 | finishArgs.push(target); 7 | finishCalled.push(element.key); 8 | }; 9 | return element; 10 | } 11 | 12 | class X { 13 | @finish x() {} 14 | @finish static a() {} 15 | @finish y() {} 16 | } 17 | 18 | assert.deepEqual(['x', 'a', 'y'], finishCalled); 19 | assert.deepEqual([X, X, X], finishArgs); 20 | -------------------------------------------------------------------------------- /examples/method-decorators/obj-spread.js: -------------------------------------------------------------------------------- 1 | /* @transform object-rest-spread */ 2 | function id(a) { 3 | return a; 4 | } 5 | 6 | var old = { 7 | a: 5, 8 | b: 9, 9 | @id f() { return 'foo'; }, 10 | @id g() { return 7; }, 11 | c: 13 12 | } 13 | var obj = { 14 | a: 10, 15 | ...old, 16 | @id f() { return 42; }, 17 | b: 20 18 | }; 19 | 20 | assert.equal(42, obj.f()); 21 | assert.equal(20, obj.b); 22 | assert.equal(7, obj.g()); 23 | assert.equal(13, obj.c); 24 | 25 | // `.a` should be overwritten with the value from `old` 26 | assert.equal(5, obj.a); 27 | -------------------------------------------------------------------------------- /examples/method-decorators/symbol-key.js: -------------------------------------------------------------------------------- 1 | const m = Symbol('m'); 2 | 3 | function d(element) { 4 | assert.equal(element.key, m); 5 | // Even though we can't detect this statically, we should be coalescing. 6 | assert.hasType(Function, element.descriptor.get); 7 | assert.hasType(Function, element.descriptor.set); 8 | return element; 9 | } 10 | 11 | // To force a "complex" expression in the bracket 12 | function id(x) { 13 | return x; 14 | } 15 | 16 | class X { 17 | // Using a symbol because syntax-decorators can't deal with symbol methods 18 | @d get [id(m)]() {} 19 | set [m](n) {} 20 | } 21 | -------------------------------------------------------------------------------- /examples/method-decorators/extras.js: -------------------------------------------------------------------------------- 1 | function d(e) { 2 | return { 3 | kind: 'property', 4 | key: e.key, 5 | descriptor: e.descriptor, 6 | isStatic: e.isStatic, 7 | extras: [ 8 | { 9 | kind: 'property', 10 | key: 'dMethod', 11 | isStatic: true, 12 | descriptor: { 13 | enumerable: true, 14 | writeable: true, 15 | configurable: true, 16 | value: e.key, 17 | } 18 | } 19 | ] 20 | }; 21 | } 22 | 23 | class X { 24 | @d m() { return 42; } 25 | } 26 | 27 | assert.equal('m', X.dMethod); 28 | assert.hasType(Function, X.prototype.m); 29 | assert.equal(42, new X().m()); 30 | -------------------------------------------------------------------------------- /examples/method-decorators/reflect-metadata.js: -------------------------------------------------------------------------------- 1 | // Replacement for the decorator that ships with current reflect-metadata 2 | function metadata(key, value) { 3 | return function decorate(element) { 4 | if (element.kind === 'property') { 5 | element.finisher = function addMetaData(klass) { 6 | Reflect.defineMetadata(key, value, klass.prototype, element.key); 7 | }; 8 | } else if (element.kind === 'class') { 9 | element.finisher = function addMetaData(klass) { 10 | Reflect.defineMetadata(key, value, klass, undefined); 11 | }; 12 | } 13 | return element; 14 | }; 15 | } 16 | 17 | @metadata('class-key', 'class-value') 18 | class C { 19 | @metadata('method-key', 'method-value') 20 | method() { 21 | } 22 | } 23 | 24 | var obj = new C(); 25 | 26 | var metadataValue = Reflect.getMetadata('method-key', obj, 'method'); 27 | assert.equal('method-value', metadataValue); 28 | 29 | var classValue = Reflect.getMetadata('class-key', C); 30 | assert.equal('class-value', classValue); 31 | -------------------------------------------------------------------------------- /examples/method-decorators/coalesce.js: -------------------------------------------------------------------------------- 1 | var descriptors = []; 2 | function d(element) { 3 | var descriptor = element.descriptor; 4 | descriptors.push(descriptor); 5 | return element; 6 | } 7 | 8 | class X { 9 | @d get a() {} 10 | 11 | @d a() {} 12 | 13 | @d set a(n) {} 14 | b() {} // should not reset 15 | static a() {} // should not reset 16 | get a() {} 17 | 18 | a() {} // reset 19 | 20 | @d get a() {} 21 | get a() {} // overrides earlier `get` 22 | @d set a(n) {} 23 | 24 | a() {} // reset 25 | } 26 | 27 | function hasFunctions(descriptor, fns) { 28 | ['value','get','set'].forEach(function check(key) { 29 | if (fns.indexOf(key) !== -1) { 30 | assert.hasType(Function, descriptor[key]); 31 | } else { 32 | assert.equal('Descriptor has no ' + key, 'undefined', typeof descriptor[key]); 33 | } 34 | }); 35 | } 36 | 37 | // There's no setter before the first descriptor gets "reset" 38 | hasFunctions(descriptors[0], ['get']); 39 | 40 | // The next descriptor is for the method 41 | hasFunctions(descriptors[1], ['value']); 42 | 43 | // The next descriptor combines the annotated setter and the following getter 44 | hasFunctions(descriptors[2], ['get', 'set']); 45 | 46 | // The first getter gets dropped, the following gets combined with the setter. 47 | // This means that *in the end* only one of the getter/setters is decorated 48 | hasFunctions(descriptors[3], ['get', 'set']); 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `babel-plugin-transform-decorators-stage-2-initial` 2 | 3 | **Warning:** This transform currently tries to optimize for matching the spec as closely as possible. It is not fast or produces pretty output. 4 | 5 | **Warning:** The proposal is still very much in flux and contains some ambiguity. The implementation in this repo is just one person's interpretation. 6 | 7 | **Warning:** If you're using the [`object-rest-spread` transform](http://babeljs.io/docs/plugins/transform-object-rest-spread/), make sure it runs before the decorator transform. Otherwise it will not work correctly. 8 | 9 | Babel transform for the [stage 2 decorators proposal](http://tc39.github.io/proposal-decorators/#sec-decorate-element). 10 | 11 | ``` 12 | npm install --save-dev babel-plugin-transform-decorators-stage-2-initial 13 | ``` 14 | 15 | ### Known Gaps 16 | 17 | This transform will generally not work without also transforming the class declaration itself, e.g. with [`transform-es2015-classes`](https://www.npmjs.com/package/babel-plugin-transform-es2015-classes). 18 | It *might* work when native `Reflect.construct` is used (e.g. node 6+) instead of a polyfill. 19 | 20 | ### References 21 | 22 | * [Notes by TC39 2016-07-28](https://github.com/jmdyck/tc39-notes/blob/master/es7/2016-07/jul-28.md#9iiic-decorators) 23 | * [`proposal-decorators`](https://github.com/tc39/proposal-decorators) 24 | * [Rendered proposal](https://tc39.github.io/proposal-decorators/) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Jan Krems 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-decorators-stage-2-initial", 3 | "version": "0.0.0", 4 | "description": "Babel Plugin Transform Decorators Stage 2 Initial", 5 | "license": "BSD-3-Clause", 6 | "main": "lib/babel-plugin-transform-decorators-stage-2-initial.js", 7 | "homepage": "https://github.com/jkrems/babel-plugin-transform-decorators-stage-2-initial", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+ssh://git@github.com/jkrems/babel-plugin-transform-decorators-stage-2-initial" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/jkrems/babel-plugin-transform-decorators-stage-2-initial/issues" 14 | }, 15 | "scripts": { 16 | "pretest": "eslint lib test", 17 | "test": "mocha", 18 | "posttest": "nlm verify" 19 | }, 20 | "nlm": { 21 | "license": { 22 | "files": [ 23 | "lib" 24 | ] 25 | } 26 | }, 27 | "dependencies": { 28 | "babel-helper-explode-class": "^6.8.0", 29 | "babel-plugin-syntax-decorators": "^6.8.0", 30 | "babel-template": "^6.9.0" 31 | }, 32 | "devDependencies": { 33 | "assertive": "^2.0.0", 34 | "babel-core": "^6.11.4", 35 | "babel-plugin-transform-class-properties": "^6.11.5", 36 | "babel-plugin-transform-es2015-classes": "^6.9.0", 37 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 38 | "core-js": "^2.4.1", 39 | "eslint": "^2.0.0", 40 | "eslint-config-groupon-es5": "^3.0.0", 41 | "eslint-plugin-import": "^1.6.1", 42 | "glob": "^7.0.5", 43 | "mocha": "^3.0.2", 44 | "nlm": "^2.0.0", 45 | "reflect-metadata": "^0.1.8" 46 | }, 47 | "author": { 48 | "name": "Jan Krems", 49 | "email": "jan.krems@gmail.com" 50 | }, 51 | "files": [ 52 | "*.js", 53 | "lib" 54 | ], 55 | "publishConfig": { 56 | "registry": "https://registry.npmjs.org" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/class-decorators/wrap-constructor.js: -------------------------------------------------------------------------------- 1 | var logs = []; 2 | function log(msg) { 3 | logs.push(msg); 4 | } 5 | 6 | function deprecate(message) { 7 | return desc => { 8 | const PrevCtor = desc.constructor; 9 | desc.constructor = function Deprecated() { 10 | log(message); 11 | return Reflect.construct(PrevCtor, arguments, this.constructor); 12 | }; 13 | return desc; 14 | }; 15 | } 16 | 17 | @deprecate('Stop using Base') 18 | class Base { 19 | constructor(arg) { 20 | this.base = arg; 21 | } 22 | 23 | static staticBase() { return 'base7'; } 24 | } 25 | 26 | @deprecate('Stop using Derived') 27 | @deprecate('Seriously, stop!') 28 | class Derived extends Base { 29 | constructor(derivedArg, baseArg) { 30 | super(baseArg); 31 | this.derived = derivedArg; 32 | } 33 | 34 | static s() { return 7; } 35 | 36 | f() { return 13; } 37 | } 38 | 39 | logs = []; 40 | const baseInst = new Base(42); 41 | assert.equal(42, baseInst.base); 42 | assert.expect('baseInst is a Base', baseInst instanceof Base); 43 | assert.deepEqual(['Stop using Base'], logs); 44 | 45 | logs = []; 46 | const inst = new Derived('x', 'y'); 47 | assert.equal('x', inst.derived); 48 | assert.equal('y', inst.base); 49 | assert.equal(13, inst.f()); 50 | assert.expect('inst is a Base', inst instanceof Base); 51 | assert.expect('inst is a Derived', inst instanceof Derived); 52 | assert.deepEqual(['Stop using Derived', 'Seriously, stop!', 'Stop using Base'], logs); 53 | 54 | assert.equal('Preserves static methods', 7, Derived.s()); 55 | assert.equal('Preserves static method inheritance', 'base7', Derived.staticBase()); 56 | 57 | assert.equal('The .name stays the same', 'Base', Base.name); 58 | assert.equal('The .name stays the same', 'Derived', Derived.name); 59 | 60 | assert.throws('Calling the wrapper as a function should throw', () => Base()); 61 | assert.throws('Calling the wrapper as a function should throw', () => Derived()); 62 | -------------------------------------------------------------------------------- /test/decorators.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var vm = require('vm'); 4 | 5 | var assert = require('assertive'); 6 | var babel = require('babel-core'); 7 | var glob = require('glob'); 8 | require('core-js/library/es6/reflect'); 9 | var Reflect = require('core-js/library/es7/reflect'); 10 | 11 | var babelPluginTransformDecoratorsStage2Initial = require('../'); 12 | 13 | var TEST_CASES = glob.sync('examples/**/*.js'); 14 | 15 | var HAS_NATIVE_REFLECT = 16 | Reflect.construct.toString().indexOf('{ [native code] }') !== -1; 17 | 18 | function toES6Code(source, useNativeClasses) { 19 | var requiresTransform = source.match(/@transform ([\w-]+)/); 20 | var additionalTransform = requiresTransform && requiresTransform[1]; 21 | return babel.transform(source, { 22 | plugins: [].concat( 23 | additionalTransform ? ['transform-' + additionalTransform] : [], 24 | babelPluginTransformDecoratorsStage2Initial, 25 | useNativeClasses ? [] : ['transform-es2015-classes'] 26 | ) 27 | }).code; 28 | } 29 | 30 | describe('decorators', function () { 31 | TEST_CASES.forEach(function (filename) { 32 | describe(filename, function () { 33 | var source; 34 | before('load', function () { 35 | source = fs.readFileSync(filename, 'utf8'); 36 | }); 37 | 38 | function runFile(useNativeClasses) { 39 | var es6Code = toES6Code(source, useNativeClasses); 40 | vm.runInNewContext('"use strict";\n' + es6Code, { 41 | assert: assert, 42 | console: console, 43 | Reflect: Reflect 44 | }, { filename: filename }); 45 | } 46 | 47 | it('runs successfully', function () { 48 | runFile(); 49 | }); 50 | 51 | it('runs without class transform', function () { 52 | if (!HAS_NATIVE_REFLECT) { 53 | this.skip(); 54 | return; 55 | } 56 | runFile(true); 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Contributing 4 | 5 | 🎉🏅 Thanks for helping us improve this project! 🙏 6 | 7 | This document outlines some of the practices we care about. 8 | If you have any questions or suggestions about the process, 9 | feel free to [open an issue](#reporting-issues) 10 | . 11 | 12 | ## How Can I Contribute? 13 | 14 | ### Reporting Issues 15 | 16 | If you find any mistakes in the docs or a bug in the code, 17 | please [open an issue in Github](https://github.com/jkrems/babel-plugin-transform-decorators-stage-2-initial/issues/new) so we can look into it. 18 | You can also [create a PR](#contributing-code) fixing it yourself, or course. 19 | 20 | If you report a bug, please follow these guidelines: 21 | 22 | * Make sure the bug exists in the latest version. 23 | * Include instructions on how to reproduce the issue. 24 | The instructions should be as minimal as possible 25 | and answer the three big questions: 26 | 1. What are the exact steps you took? This includes the exact versions of node, npm, and any packages involved. 27 | 1. What result are you expecting? 28 | 1. What is the actual result? 29 | 30 | ### Improving Documentation 31 | 32 | For small documentation changes, you can use [Github's editing feature](https://help.github.com/articles/editing-files-in-another-user-s-repository/). 33 | The only thing to keep in mind is to prefix the commit message with "docs: ". 34 | The detault commit message generated by Github will lead to a failing CI build. 35 | 36 | For larger updates to the documentation 37 | it might be better to follow the [instructions for contributing code below](#contributing-code). 38 | 39 | ### Contributing Code 40 | 41 | **Note:** If you're planning on making substantial changes, 42 | please [open an issue first to discuss your idea](#reporting-issues). 43 | Otherwise you might end up investing a lot of work 44 | only to discover that it conflicts with plans the maintainers might have. 45 | 46 | The general steps for creating a pull request are: 47 | 48 | 1. Create a branch for your change. 49 | Always start your branch from the latest `master`. 50 | We often prefix the branch name with our initials, e.g. `jk-a-change`. 51 | 1. Run `npm install` to install the dependencies. 52 | 1. If you're fixing a bug, be sure to write a test *first*. 53 | That way you can validate that the test actually catches the bug and doesn't pass. 54 | 1. Make your changes to the code. 55 | Remember to update the tests if you add new features or change behavior. 56 | 1. Run the tests via `npm test`. This will also run style checks and other validations. 57 | You might see errors about uncommitted files. 58 | This is expected until you commit your changes. 59 | 1. Once you're done, `git add .` and `git commit`. 60 | Please follow the [commit message conventions](#commits--commit-messages) described below. 61 | 1. Push your branch to Github & create a PR. 62 | 63 | #### Code Style 64 | 65 | In addition to any linting rules the project might include, 66 | a few general rules of thumb: 67 | 68 | * Try to match the style of the rest of the code. 69 | * We prefer simple code that is easy to understand over terse, expressive code. 70 | * We try to structure projects by semantics instead of role. 71 | E.g. we'd rather have a `tree.js` module that contains tree traversal-related helpers 72 | than a `helpers.js` module. 73 | * Actually, if you create helpers you might want to put those into a separate package. 74 | That way it's easier to reuse them. 75 | 76 | #### Commits & Commit Messages 77 | 78 | Please follow the [angular commit message conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines). 79 | We use an automated tool for generating releases 80 | that depends on the conventions to determine the next version and the content of the changelog. 81 | Commit messages that don't follow the conventions will cause `npm test` (and thus CI) to fail. 82 | 83 | The short summary - a commit message should look like this: 84 | 85 | ``` 86 | : 87 | 88 | 89 | 90 | 91 | 92 |