├── .eslintignore ├── index.js ├── .gitignore ├── .npmignore ├── .babelrc ├── .travis.yml ├── src ├── helpers.js ├── @abstract.js ├── @memoizator.js ├── restLengthEqualTo.js ├── @passedValuesEqualToNumberOfArguments.js ├── @timers.js ├── @deprecated.js ├── @stators.js ├── @trycatch.js ├── @autobind.js ├── @before.js ├── @after.js ├── @compose.js ├── @immutablors.js ├── @inheritedfunctions.js ├── @executors.js ├── @multiinheritance.js ├── index.js ├── @loggers.js ├── @validators.js └── validationHelpers.js ├── .eslintrc.js ├── test ├── donoteval.js ├── stators.js ├── abstract.js ├── compose.js ├── deprecated.js ├── immutable.js ├── passedValuesEqualToNumberOfArguments.js ├── multiinheirtance.js ├── trycatch.js ├── after.js ├── before.js ├── executors.js ├── inherittedfunctions.js ├── loggers.js └── validations.js ├── CHANGELOG.md ├── package.json ├── EXAMPLES.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | ./test/*.js 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require( './lib' ); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /lib 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .eslintignore 3 | .eslintrc 4 | .gitignore 5 | .travis.yml 6 | npm-debug.log 7 | test/ 8 | app/ 9 | src/ 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015","stage-0"], 3 | "plugins": [ 4 | ["transform-decorators-legacy"], 5 | ["transform-decorators"], 6 | ["syntax-object-rest-spread"], 7 | ["transform-object-rest-spread"] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | notifications: 3 | email: false 4 | language: node_js 5 | node_js: 6 | - "8" 7 | before_script: 8 | - yarn --ignore-engines 9 | after_success: 10 | - 'curl -Lo travis_after_all.py https://git.io/travis_after_all' 11 | - python travis_after_all.py 12 | - export $(cat .to_export_back) &> /dev/null 13 | branches: 14 | only: 15 | - master 16 | - /^greenkeeper/.*$/ 17 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | 8 | import { _isFunction } from './validationHelpers'; 9 | 10 | export const descriptorIsFunc = function (key, func) { 11 | if (!_isFunction(func)) { 12 | throw Error(`${ key } is not a function!`); 13 | } 14 | return true; 15 | }; 16 | 17 | export const noop = function () {}; 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "avraam", 3 | "rules": { 4 | "class-methods-use-this": 0, 5 | "import/no-unresolved": 0, 6 | "import/prefer-default-export": 0, 7 | "no-underscore-dangle": 0, 8 | "no-param-reassign": 0, 9 | "no-plusplus": 1, 10 | "no-restricted-syntax": 1, 11 | "no-prototype-builtins": 1, 12 | "import/no-cycle": 1, 13 | "no-restricted-globals": 1, 14 | } 15 | } -------------------------------------------------------------------------------- /src/@abstract.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Decorator that makes a class abstract 3 | * ( it cannot being instantiated directly ) 4 | * 5 | * @method _abstract 6 | * 7 | * @return { Class } decorator function 8 | */ 9 | export const _abstract = function abstract() { 10 | return function abstractTarget(target) { 11 | return class Abstract extends target { 12 | constructor() { 13 | super(); 14 | if (new.target === Abstract) { 15 | throw Error(`The ${ target.name } is an abstract class`); 16 | } 17 | } 18 | }; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /test/donoteval.js: -------------------------------------------------------------------------------- 1 | // import { expect } from 'chai'; 2 | // import { _donoteval as donoteval } from '../src/@donoteval.js'; 3 | // 4 | // class Person { 5 | // @donoteval() 6 | // doSomething( a, b ) { 7 | // eval( a + '+' + b ); 8 | // } 9 | // 10 | // }; 11 | // 12 | // describe( 'donoteval tests', function () { 13 | // let p; 14 | // beforeEach( function () { 15 | // p = new Person(); 16 | // } ); 17 | // 18 | // // @donoteval 19 | // it( '@donoteval: it should execute the function only once', function () { 20 | // expect( p.doSomething( 1, 2 ) ).to.equal( 3 ); 21 | // } ); 22 | // 23 | // 24 | // }); 25 | -------------------------------------------------------------------------------- /src/@memoizator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Memoization decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | 9 | export const _memoization = function () { 10 | const cache = new Map(); 11 | 12 | return function (key, target, descriptor) { 13 | const func = descriptor.value; 14 | descriptorIsFunc(key, func); 15 | descriptor.value = function (...args) { 16 | const ckey = args.join(''); 17 | if (cache.has(ckey)) { 18 | return cache.get(ckey); 19 | } 20 | const res = func.apply(this, args); 21 | cache.set(ckey, res); 22 | return res; 23 | }; 24 | return descriptor; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/restLengthEqualTo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validates that the number of ...rest values 3 | * is equal to the specified number. 4 | * 5 | * e.g. @restLengthEqualTo(2) expects 2 values as ...rest 6 | * 7 | * @author Avraam Mavridis 8 | * 9 | */ 10 | export default function restLengthEqualTo(restLength) { 11 | return function restLengthEqualToTarget(key, target, descriptor) { 12 | const func = descriptor.value; 13 | descriptor.value = function descriptorValue(...rest) { 14 | if ((rest.length - func.length) !== restLength) { 15 | throw Error(`Number of rest values is not equal to ${ restLength }`); 16 | } 17 | func.call(this, rest); 18 | }; 19 | return descriptor; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /test/stators.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import { readonly, enumerable, nonenumerable, nonconfigurable, configurable } from '../src/index.js'; 3 | 4 | class Person { 5 | @readonly() 6 | prop1 = 10; 7 | 8 | @nonconfigurable() 9 | prop2 = {}; 10 | }; 11 | 12 | describe( 'stators tests', function () { 13 | let p; 14 | let p2; 15 | beforeEach( function () { 16 | p = new Person(); 17 | } ); 18 | 19 | // @readonly 20 | it( '@readonly: should not set a readonly property', function () { 21 | expect( () => p.prop1 = 15 ).to.throw( Error ); 22 | } ); 23 | 24 | // @nonconfigurable 25 | it( '@nonconfigurable: should not set a readonly property', function () { 26 | expect( () => delete p.prop2 ).to.throw( Error ); 27 | } ); 28 | 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/@passedValuesEqualToNumberOfArguments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validates that the number of values passed to a function 3 | * is equal to the number of arguments that the function accepts. 4 | * 5 | * @author Avraam Mavridis 6 | * 7 | */ 8 | export const _passedValuesEqualToNumberOfArguments = function (failSilent = false) { 9 | return function (target, key, descriptor) { 10 | const func = descriptor.value; 11 | descriptor.value = function (...args) { 12 | if (func.length !== args.length) { 13 | // eslint-disable-next-line 14 | if (failSilent) return; 15 | throw Error(`Only ${ func.length } values should be passed to the function`); 16 | } 17 | return func.apply(this, args); 18 | }; 19 | return descriptor; 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /test/abstract.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { abstract } from '../src/index.js'; 3 | 4 | 5 | @abstract() 6 | class Person { t() { return 3; }} 7 | 8 | 9 | class Person2 extends Person { r(){ return 10;} } 10 | 11 | 12 | describe( '@abstract tests', function () { 13 | let p, c; 14 | beforeEach( function () { 15 | c = new Person2(); 16 | } ); 17 | 18 | it( 'Abstract classes should not being able to instantiated directly', function () { 19 | const instantiateAbstract = () => { 20 | p = new Person(); 21 | }; 22 | expect( instantiateAbstract ).to.throw( Error ); 23 | } ); 24 | 25 | it( 'Dirived classes should be able to inherit from abstract classes', function () { 26 | expect( c.t() ).to.equal( 3 ); 27 | expect( c.r() ).to.equal( 10 ); 28 | } ); 29 | } ); 30 | -------------------------------------------------------------------------------- /test/compose.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { compose, leftCompose } from '../src/index.js'; 3 | 4 | const r = (t) => t * 5; 5 | const r2 = (t) => t + 3; 6 | 7 | class Person { 8 | @compose([r, r2]) 9 | doSomething( a, b ) { 10 | return 2; 11 | } 12 | 13 | @leftCompose([r, r2]) 14 | doSomething2( a, b ) { 15 | return 2; 16 | } 17 | }; 18 | 19 | describe( 'loggers tests', function () { 20 | let p; 21 | beforeEach( function () { 22 | p = new Person(); 23 | } ); 24 | 25 | // @compose 26 | it( '@compose: should right-compose the methods correctly', function () { 27 | expect( p.doSomething( ) ).to.equal( 13 ); 28 | } ); 29 | 30 | it( '@compose: should left-compose the methods correctly', function () { 31 | expect( p.doSomething2( ) ).to.equal( 25 ); 32 | } ); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /src/@timers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timing related decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | /** 9 | * Timeout decorator 10 | * 11 | * @method _timeout 12 | * 13 | * @param { number } wait = 300 14 | * 15 | */ 16 | const __timeout = function timeout(wait = 300) { 17 | const debounceKeys = {}; 18 | 19 | return function timeoutTarget(key, target, descriptor) { 20 | const func = descriptor.value; 21 | descriptorIsFunc(key, func); 22 | const dkey = Symbol('dkey'); 23 | descriptor.value = function descriptorValue(...args) { 24 | debounceKeys[dkey] = setTimeout(() => { 25 | delete debounceKeys[dkey]; 26 | func.apply(this, args); 27 | }, wait); 28 | }; 29 | return descriptor; 30 | }; 31 | }; 32 | 33 | export const _timeout = __timeout; 34 | export const _debounce = __timeout; 35 | export const _defer = __timeout.bind(this, 0); 36 | -------------------------------------------------------------------------------- /test/deprecated.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | import sinonChai from 'sinon-chai'; 4 | import { deprecated } from '../src/index.js'; 5 | 6 | chai.use( sinonChai ); 7 | 8 | class Person { 9 | @deprecated( 'Function is deprecated' ) 10 | doSomething( a, b ) { 11 | return a + b; 12 | } 13 | 14 | }; 15 | 16 | describe( 'Deprecation tests', function () { 17 | let p; 18 | beforeEach( function () { 19 | p = new Person(); 20 | sinon.spy( console, 'warn' ); 21 | } ); 22 | 23 | afterEach(function() { 24 | console.warn.restore(); 25 | }); 26 | 27 | // @deprecated 28 | it( '@deprecated: should not affect the function results', function () { 29 | expect( p.doSomething( 1, 2 ) ).to.equal( 3 ); 30 | } ); 31 | 32 | it( '@deprecated: should call the console.warn methond', function () { 33 | p.doSomething( 1, 2 ) 34 | expect( console.warn ).to.have.been.called; 35 | } ); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /src/@deprecated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Deprecated decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | import { _isString } from './validationHelpers'; 9 | 10 | /** 11 | * Deprecated decorator 12 | * 13 | * @method _deprecated 14 | * 15 | * 16 | * @return { function } decorator function 17 | */ 18 | export const _deprecated = function deprecated(msg) { 19 | if (!_isString(msg)) { 20 | throw Error('Warning message should be a string.'); 21 | } 22 | return function deprecatedTarget(target, key, descriptor) { 23 | const func = descriptor.value; 24 | descriptorIsFunc(target, func); 25 | const { name } = target.constructor; 26 | descriptor.value = function decriptorValue(...args) { 27 | // eslint-disable-next-line 28 | console.warn(`${ name }#${ key } : ${ msg }`); 29 | return func.apply(this, args); 30 | }; 31 | return descriptor; 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/@stators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stators decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | 8 | export const _enumerable = function enumerable() { 9 | return function enumerableTarget(key, target, descriptor) { 10 | descriptor.enumerable = true; 11 | return descriptor; 12 | }; 13 | }; 14 | 15 | export const _nonenumerable = function nonenumerable() { 16 | return function nonenumerableTarget(key, target, descriptor) { 17 | descriptor.enumerable = false; 18 | return descriptor; 19 | }; 20 | }; 21 | 22 | export const _readonly = function readonly() { 23 | return function readonlyTarget(key, target, descriptor) { 24 | descriptor.writable = false; 25 | return descriptor; 26 | }; 27 | }; 28 | 29 | export const _nonconfigurable = function nonconfigurable() { 30 | return function nonconfigurableTarget(key, target, descriptor) { 31 | descriptor.configurable = false; 32 | return descriptor; 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/@trycatch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Try catch Decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | import { _isFunction } from './validationHelpers'; 9 | 10 | /** 11 | * Try-catch decorator 12 | * 13 | * @method _timeout 14 | * 15 | * @param { func } errorHandler 16 | * 17 | */ 18 | export const _trycatch = function (errorHandler) { 19 | if (!_isFunction(errorHandler)) { 20 | throw Error(`The ErrorHandler should be a function. ${ JSON.stringify(errorHandler) } is not a function`); 21 | } 22 | 23 | return function (key, target, descriptor) { 24 | const func = descriptor.value; 25 | descriptorIsFunc(key, func); 26 | descriptor.value = function (...args) { 27 | let res; 28 | try { 29 | res = func.apply(this, args); 30 | } catch (e) { 31 | errorHandler(e); 32 | } 33 | return res; 34 | }; 35 | return descriptor; 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /test/immutable.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { immutable, doesNotMutate } from '../src/index.js'; 3 | 4 | class Person { 5 | @immutable() 6 | mutateObject( obj ) { 7 | obj.something = 5; 8 | return obj; 9 | } 10 | 11 | @doesNotMutate() 12 | mutateObject2( obj ) { 13 | obj.something = 5; 14 | return obj; 15 | } 16 | }; 17 | 18 | describe( 'immutablors tests', function () { 19 | let p; 20 | beforeEach( function () { 21 | p = new Person(); 22 | } ); 23 | 24 | // @immutable 25 | it( '@immutable: it should execute the function without mutating the passed values', function () { 26 | let c = { something: 10 }; 27 | expect( p.mutateObject( c ) ).to.not.equal( c ); 28 | } ); 29 | 30 | // @doesNotMutate 31 | it( '@doesNotMutate: it should execute the function without mutating the passed values', function () { 32 | let d = { something: 10 }; 33 | expect( p.mutateObject2.bind( this, d ) ).to.throw( Error ); 34 | } ); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /src/@autobind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @autobind decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | 9 | /** 10 | * Autobind function decorator 11 | * 12 | * @method _autobind 13 | * @param { function } 14 | * 15 | * @return { function } decorator function 16 | */ 17 | export const _autobind = function autobind() { 18 | return function autobindTarget(target, key, descriptor) { 19 | const func = descriptor.value; 20 | descriptorIsFunc(key, func); 21 | delete descriptor.writable; 22 | delete descriptor.value; 23 | 24 | return { 25 | ...descriptor, 26 | get() { 27 | if (this === target.prototype || this.hasOwnProperty(key)) { 28 | return func; 29 | } 30 | 31 | Object.defineProperty(this, key, { 32 | value: func, 33 | configurable: true, 34 | writable: true 35 | }); 36 | return func; 37 | }, 38 | }; 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /src/@before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @before decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { _isPromise, _isFunction } from './validationHelpers'; 8 | import { descriptorIsFunc } from './helpers'; 9 | 10 | /** 11 | * Before function decorator 12 | * 13 | * @method _before 14 | * @param { function } 15 | * 16 | * @return { function } decorator function 17 | */ 18 | export const _before = function (beforeFunc) { 19 | if (!_isFunction(beforeFunc)) { 20 | throw Error('a function should be passed to the @before decorator'); 21 | } 22 | return function (key, target, descriptor) { 23 | const func = descriptor.value; 24 | descriptorIsFunc(key, func); 25 | descriptor.value = function (...args) { 26 | const beforeFuncRes = beforeFunc(); 27 | const res = func.apply(this, args); 28 | if (_isPromise(beforeFuncRes)) { 29 | return beforeFuncRes.then(() => res); 30 | } 31 | 32 | return res; 33 | }; 34 | return descriptor; 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/@after.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @after decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { _isPromise, _isFunction } from './validationHelpers'; 8 | import { descriptorIsFunc } from './helpers'; 9 | 10 | /** 11 | * Base decorator function for immutability 12 | * 13 | * @method _basefunc 14 | * 15 | * 16 | * @return { function } decorator function 17 | */ 18 | export const _after = function after(afterFunc) { 19 | if (!_isFunction(afterFunc)) { 20 | throw Error('a function should be passed to the @after decorator'); 21 | } 22 | return function afterTarget(target, key, descriptor) { 23 | const func = descriptor.value; 24 | descriptorIsFunc(key, func); 25 | descriptor.value = function descriptorValue(...args) { 26 | const res = func.apply(this, args); 27 | const afterFuncRes = afterFunc(); 28 | if (_isPromise(afterFuncRes)) { 29 | return afterFuncRes.then(() => res); 30 | } 31 | 32 | return res; 33 | }; 34 | return descriptor; 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /test/passedValuesEqualToNumberOfArguments.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { valuesEqualToNumberOfArguments } from '../src/index.js'; 3 | 4 | class Person { 5 | @valuesEqualToNumberOfArguments() 6 | doSomething( a, b ) { 7 | return a + b; 8 | } 9 | 10 | @valuesEqualToNumberOfArguments( true ) 11 | doSomething2( a, b ) { 12 | return a + b; 13 | } 14 | }; 15 | 16 | describe( 'executors tests', function () { 17 | let p; 18 | beforeEach( function () { 19 | p = new Person(); 20 | } ); 21 | 22 | // @valuesEqualToNumberOfArguments 23 | it( '@valuesEqualToNumberOfArguments: it should throw Error when the number of passed values are not equal with the number of arguments the function accepts', function () { 24 | expect( () => p.doSomething( 1 ) ).to.throw( Error ); 25 | expect( () => p.doSomething( 1,2,3,4 ) ).to.throw( Error ); 26 | } ); 27 | 28 | it( '@valuesEqualToNumberOfArguments: it should be able to fail silently', function () { 29 | expect( p.doSomething2( 1 ) ).to.equal( undefined ); 30 | } ); 31 | 32 | it( '@valuesEqualToNumberOfArguments: it should execute the function when the passed values are equal with the number of arguments', function () { 33 | expect( p.doSomething2( 1, 2 ) ).to.equal( 3 ); 34 | } ); 35 | }); 36 | -------------------------------------------------------------------------------- /test/multiinheirtance.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { multiInherit, partialyInherit } from '../src/index.js'; 3 | 4 | class Manager{ render(){ return 42; } init(){ return 1; } t(){} }; 5 | class Employee{render(){ return 43; } mount(){ return 2; } t(){}}; 6 | 7 | @multiInherit( Manager, Employee ) 8 | class Person{ t(){ return 3; }}; 9 | 10 | @partialyInherit( [ Manager, Employee ], 'render' ) 11 | class Person2{ t(){ return 3; }}; 12 | 13 | describe( 'multiinheritance tests', function () { 14 | let p,c; 15 | beforeEach( function () { 16 | p = new Person(); 17 | c = new Person2(); 18 | } ); 19 | 20 | // @multiInherit 21 | it( '@multiInherit: should inherit all the methods from the base classes', function () { 22 | expect( p.render() ).to.equal( 43 ); 23 | expect( p.init() ).to.equal( 1 ); 24 | expect( p.mount() ).to.equal( 2 ); 25 | expect( p.t() ).to.equal( 3 ); 26 | } ); 27 | 28 | // @partialyInherit 29 | it( '@partialyInherit: should inherit only the specified classes', function () { 30 | expect( c.render ).to.not.equal( undefined ); 31 | expect( c.render() ).to.equal( 43 ); 32 | expect( c.init ).to.equal( undefined ); 33 | expect( c.mount ).to.equal( undefined ); 34 | expect( c.t() ).to.equal( 3 ); 35 | } ); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /src/@compose.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @compose decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { _isFunction } from './validationHelpers'; 8 | import { descriptorIsFunc } from './helpers'; 9 | 10 | /** 11 | * Compose decorator 12 | * 13 | * @method _compose 14 | * 15 | * 16 | * @return { function } decorator function 17 | */ 18 | const __compose = function (_meths, composeType) { 19 | if (composeType === 'LEFT_COMPOSE') { 20 | _meths.reverse(); 21 | } 22 | const meths = [].concat(_meths); 23 | meths.forEach(meth => { 24 | if (!_isFunction(meth)) { 25 | throw Error(`${ meth.constructor.name } is not a function`); 26 | } 27 | }); 28 | 29 | return function (target, key, descriptor) { 30 | const func = descriptor.value; 31 | descriptorIsFunc(key, func); 32 | descriptor.value = function (...args) { 33 | const initres = func.apply(this, args); 34 | const res = meths.reduce((previousValue, currentMeth) => currentMeth(previousValue), initres); 35 | 36 | return res; 37 | }; 38 | }; 39 | }; 40 | 41 | export const _compose = function (_meths) { 42 | return __compose(_meths, 'RIGHT_COMPOSE'); 43 | }; 44 | 45 | export const _leftCompose = function (_meths) { 46 | return __compose(_meths, 'LEFT_COMPOSE'); 47 | }; 48 | -------------------------------------------------------------------------------- /test/trycatch.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | import sinonChai from 'sinon-chai'; 4 | import { trycatch } from '../src/index.js'; 5 | 6 | chai.use( sinonChai ); 7 | 8 | class Person { 9 | @trycatch( (e) => console.log(e) ) 10 | doSomething() { 11 | return 42; 12 | } 13 | 14 | @trycatch( (e) => console.log(e) ) 15 | doSomething2() { 16 | throw Error(); 17 | } 18 | }; 19 | 20 | 21 | 22 | describe( 'trycatch tests', function () { 23 | let p; 24 | beforeEach( function () { 25 | p = new Person(); 26 | sinon.spy( console, 'log' ); 27 | } ); 28 | 29 | afterEach(function() { 30 | console.log.restore(); 31 | }); 32 | 33 | // @trycatch 34 | it( '@trycatch: should call the method correctly if there is no error', function () { 35 | expect( p.doSomething() ).to.equal( 42 ); 36 | } ); 37 | 38 | it( '@trycatch: should call the error handler if there is error', function () { 39 | p.doSomething2() 40 | expect( console.log ).to.be.called; 41 | } ); 42 | 43 | it( '@trycatch: should throw an exception if the passed errorHandler is not a function', function () { 44 | expect( function(){ 45 | class Person2{ 46 | @trycatch( 'dosmething' ) 47 | doSomething3() {} 48 | } 49 | } ).to.throw( Error ); 50 | } ); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /test/after.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | import sinonChai from 'sinon-chai'; 4 | import { after } from '../src/index.js'; 5 | 6 | 7 | chai.use( sinonChai ); 8 | 9 | const _aft = function(){ console.log('>>>>>>>>>>>'); } 10 | const _aft2 = function(){ return Promise.resolve( 42 ) } 11 | 12 | class Person { 13 | @after( _aft ) 14 | doSomething( a, b ) { 15 | return a + b; 16 | } 17 | 18 | @after( _aft2 ) 19 | doSomething2( a, b ) { 20 | return a + b; 21 | } 22 | }; 23 | 24 | describe( 'loggers tests', function () { 25 | let p; 26 | beforeEach( function () { 27 | p = new Person(); 28 | sinon.spy( console, 'log' ); 29 | } ); 30 | 31 | afterEach(function() { 32 | console.log.restore(); 33 | }); 34 | 35 | // @after 36 | it( '@after: should not affect the function results', function () { 37 | expect( p.doSomething( 1, 2 ) ).to.equal( 3 ); 38 | } ); 39 | 40 | it( '@after: should not call the console methods', function () { 41 | p.doSomething( 1, 2 ) 42 | expect( console.log ).to.be.called; 43 | } ); 44 | 45 | it( '@after: should return a promise if @after returns a promise, when the promise resolve returns the value of the actual method call', function ( done ) { 46 | p.doSomething2( 1, 2 ).then( function( result ){ 47 | expect( result ).to.equal( 3 ); 48 | done(); 49 | } ) 50 | 51 | } ); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /src/@immutablors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mutability related decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | 8 | import deepcopy from 'deepcopy'; 9 | import deepEqual from 'deep-equal'; 10 | import { descriptorIsFunc } from './helpers'; 11 | 12 | /** 13 | * Base decorator function for immutability 14 | * 15 | * @method _basefunc 16 | * 17 | * 18 | * @return { function } decorator function 19 | */ 20 | export const _immutable = function () { 21 | return function (key, target, descriptor) { 22 | const func = descriptor.value; 23 | descriptorIsFunc(key, func); 24 | descriptor.value = function (...args) { 25 | const newArgs = args.reduce((previousval, currentval) => { 26 | previousval.push(deepcopy(currentval)); 27 | return previousval; 28 | }, []); 29 | return func.apply(this, newArgs); 30 | }; 31 | return descriptor; 32 | }; 33 | }; 34 | 35 | export const _doesNotMutate = function () { 36 | return function (key, target, descriptor) { 37 | const func = descriptor.value; 38 | descriptorIsFunc(key, func); 39 | descriptor.value = function (...args) { 40 | const tempArgs = deepcopy(args); 41 | const returnValue = func.apply(this, args); 42 | if (!deepEqual(args, tempArgs)) { 43 | throw Error(`${ target } mutates the passed values`); 44 | } 45 | return returnValue; 46 | }; 47 | return descriptor; 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /src/@inheritedfunctions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | 9 | /** 10 | * The method can be called from an instance of the base 11 | * class, but cannot be called from an instance of a derived class 12 | * 13 | * @method _overridden 14 | * 15 | */ 16 | export const _overridden = function () { 17 | return function (target, key, descriptor) { 18 | const func = descriptor.value; 19 | descriptorIsFunc(key, func); 20 | descriptor.value = function (...args) { 21 | const thisPrototype = Object.getPrototypeOf(this); 22 | if (target !== thisPrototype) { 23 | throw Error(`${ thisPrototype.constructor.name } should overridde method ${ key } of the base class ${ target.constructor.name }`); 24 | } 25 | return func.call(this, args); 26 | }; 27 | return descriptor; 28 | }; 29 | }; 30 | 31 | /** 32 | * The method can not be called from an instance of the base or 33 | * derived class, it should be overridden 34 | * 35 | * @method _forceoverriden 36 | * 37 | */ 38 | export const _forceoverridden = function () { 39 | return function (target, key, descriptor) { 40 | descriptorIsFunc(key, descriptor.value); 41 | // eslint-disable-next-line 42 | descriptor.value = function (...args) { 43 | throw Error(`method ${ key } of the base class ${ target.constructor.name } should be overridden`); 44 | }; 45 | return descriptor; 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /test/before.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | import sinonChai from 'sinon-chai'; 4 | import { before } from '../src/index.js'; 5 | 6 | chai.use( sinonChai ); 7 | 8 | const _bf = function(){ console.log('>>>>>>>>>>>'); } 9 | const _bf2 = function(){ return Promise.resolve( 42 ) } 10 | 11 | class Person { 12 | @before( _bf ) 13 | doSomething( a, b ) { 14 | console.log( '42' ); 15 | return a + b; 16 | } 17 | 18 | @before( _bf2 ) 19 | doSomething2( a, b ) { 20 | return a + b; 21 | } 22 | }; 23 | 24 | describe( 'loggers tests', function () { 25 | let p; 26 | beforeEach( function () { 27 | p = new Person(); 28 | sinon.spy( console, 'log' ); 29 | } ); 30 | 31 | afterEach(function() { 32 | console.log.restore(); 33 | }); 34 | 35 | // @before 36 | it( '@before: should not affect the function results', function () { 37 | expect( p.doSomething( 1, 2 ) ).to.equal( 3 ); 38 | } ); 39 | 40 | it( '@before: should not call the console methods', function () { 41 | p.doSomething( 1, 2 ) 42 | expect( console.log ).to.be.calledWith( '>>>>>>>>>>>' ); 43 | expect( console.log ).to.be.calledWith( '42' ); 44 | } ); 45 | 46 | it( '@before: should return a promise if @before returns a promise, when the promise resolve returns the value of the actual method call', function ( done ) { 47 | p.doSomething2( 1, 2 ).then( function( result ){ 48 | expect( result ).to.equal( 3 ); 49 | done(); 50 | } ) 51 | 52 | } ); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /test/executors.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { once, times, timesCalled } from '../src/index.js'; 3 | 4 | class Person { 5 | @once() 6 | doSomething( a, b ) { 7 | return a + b; 8 | } 9 | 10 | @times(2) 11 | doSomethingElse( a, b ) { 12 | return a + b; 13 | } 14 | 15 | @timesCalled() 16 | doSomethingElseThanElse( a, b ) { 17 | return a + b; 18 | } 19 | }; 20 | 21 | describe( 'executors tests', function () { 22 | let p; 23 | beforeEach( function () { 24 | p = new Person(); 25 | } ); 26 | 27 | // @once 28 | it( '@once: it should execute the function only once', function () { 29 | expect( p.doSomething( 1, 2 ) ).to.equal( 3 ); 30 | expect( p.doSomething( 51, 2 ) ).to.equal( 3 ); 31 | expect( p.doSomething( 1, 22 ) ).to.equal( 3 ); 32 | } ); 33 | 34 | // @times 35 | it( '@times: it should execute the function n times', function () { 36 | expect( p.doSomethingElse(1,2) ).to.equal( 3 ); 37 | expect( p.doSomethingElse(3,2) ).to.equal( 5 ); 38 | expect( p.doSomethingElse(33,12) ).to.equal( 5 ); 39 | } ); 40 | 41 | // @timesCalled 42 | it( '@timesCalled: it should have a parameter indicating how many times the function has been called', function () { 43 | p.doSomethingElseThanElse(1,2); 44 | p.doSomethingElseThanElse(1,2); 45 | expect( p.doSomethingElseThanElse.timesCalled ).to.equal( 2 ); 46 | p.doSomethingElseThanElse(1,2); 47 | p.doSomethingElseThanElse(1,2); 48 | expect( p.doSomethingElseThanElse.timesCalled ).to.equal( 4 ); 49 | } ); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /src/@executors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Execution related decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | 8 | import { descriptorIsFunc } from './helpers'; 9 | 10 | /** 11 | * Executes a function n times, any repeat call returns 12 | * the value of the nth call 13 | * 14 | * @method __times 15 | * 16 | * 17 | * @return { function } decorator function 18 | */ 19 | const __times = function (times = 1) { 20 | let timescalled = 0; 21 | let res; 22 | return function (target, key, descriptor) { 23 | const func = descriptor.value; 24 | descriptorIsFunc(key, func); 25 | descriptor.value = function (...args) { 26 | if (timescalled !== times) { 27 | timescalled++; 28 | res = func.apply(this, args); 29 | } 30 | 31 | return res; 32 | }; 33 | return descriptor; 34 | }; 35 | }; 36 | 37 | /** 38 | * Attaches a property on the function indicating how many times 39 | * has been called 40 | * 41 | * @method __times 42 | * 43 | * 44 | * @return { function } decorator function 45 | */ 46 | const __timesCalled = function () { 47 | return function (target, key, descriptor) { 48 | const func = descriptor.value; 49 | descriptorIsFunc(key, func); 50 | descriptor.value = function (...args) { 51 | descriptor.value.timesCalled = descriptor.value.timesCalled || 0; 52 | descriptor.value.timesCalled++; 53 | return func.apply(this, args); 54 | }; 55 | return descriptor; 56 | }; 57 | }; 58 | 59 | export const _times = __times; 60 | export const _once = __times.bind({}, 1); 61 | export const _timesCalled = __timesCalled; 62 | -------------------------------------------------------------------------------- /test/inherittedfunctions.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { overridden, forceoverridden } from '../src/index.js'; 3 | 4 | 5 | class Person { 6 | @overridden() 7 | render() { 8 | return 42; 9 | } 10 | 11 | @forceoverridden() 12 | lol(){} 13 | }; 14 | 15 | class Manager extends Person{} 16 | 17 | class Developer extends Person{ 18 | lol(){ 19 | return 42; 20 | } 21 | } 22 | 23 | describe( 'immutablors tests', function () { 24 | let p,m,d; 25 | beforeEach( function () { 26 | p = new Person(); 27 | m = new Manager(); 28 | d = new Developer(); 29 | } ); 30 | 31 | // @overriden 32 | it( '@overridden: should not throw Error when the method is called from the base class', function () { 33 | expect( function(){ return p.render( ); } ).to.not.throw( Error ); 34 | expect( p.render( ) ).to.equal( 42 ); 35 | } ); 36 | 37 | it( '@overridden: should throw Error when the method is called from a subclass', function () { 38 | expect( function(){ return m.render( ); } ).to.throw( Error ); 39 | } ); 40 | 41 | // @forceoverridden 42 | it( '@forceoverridden: should throw an error if it is called from the base class', function () { 43 | expect( function(){ return p.lol( ); } ).to.throw( Error ); 44 | } ); 45 | 46 | it( '@forceoverridden: should throw an error if it is called from the subclass without being overriden', function () { 47 | expect( function(){ return m.lol( ); } ).to.throw( Error ); 48 | } ); 49 | 50 | it( '@forceoverridden: should not throw an error if it is called from the subclass and has being overriden', function () { 51 | expect( function(){ return d.lol( ); } ).to.not.throw( Error ); 52 | } ); 53 | }); 54 | -------------------------------------------------------------------------------- /src/@multiinheritance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Multinheritance decorator 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { _isFunction } from './validationHelpers'; 8 | 9 | 10 | const __inherit = function (_clas, _meths, _partially) { 11 | const classes = [].concat(_clas).reverse(); 12 | const methods = [].concat(_meths); 13 | return function (target) { 14 | classes.forEach(_class => { 15 | const keys = Object.getOwnPropertyNames(_class.prototype); 16 | keys.forEach(key => { 17 | if (_partially) { 18 | if (!target.prototype[key] 19 | && methods.indexOf(key) > -1 20 | && _isFunction(_class.prototype[key])) { 21 | target.prototype[key] = _class.prototype[key]; 22 | } 23 | } else if (!_partially) { 24 | if (!target.prototype[key] && _isFunction(_class.prototype[key])) { 25 | target.prototype[key] = _class.prototype[key]; 26 | } 27 | } 28 | }); 29 | }); 30 | return target; 31 | }; 32 | }; 33 | 34 | /** 35 | * Inherit all the methods of the passed classes 36 | * if two classes have method with the same name 37 | * the last one is inheritted. 38 | * 39 | * @method _multiInherit 40 | * 41 | * @param { array of classes } 42 | * 43 | * @return { class } 44 | */ 45 | export const _multiInherit = function (...args) { 46 | return __inherit(args, [], false); 47 | }; 48 | 49 | /** 50 | * Inherit only the specified classes 51 | * 52 | * @method _partialyInherit 53 | * 54 | * @param { array of classes or a class } _clas 55 | * @param { array of strings or string } _meths 56 | * 57 | * @return { class } 58 | */ 59 | export const _partialyInherit = function (_clas, _meths) { 60 | const classes = [].concat(_clas); 61 | const methods = [].concat(_meths); 62 | return __inherit(classes, methods, true); 63 | }; 64 | -------------------------------------------------------------------------------- /test/loggers.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | import sinonChai from 'sinon-chai'; 4 | import { log, loglocalstorage, donotlog, donotlogerrors, donotlogwarnings, donotlogmessages } from '../src/index.js'; 5 | 6 | 7 | chai.use( sinonChai ); 8 | 9 | class Person { 10 | @log() 11 | doSomething( a, b ) { 12 | return a + b; 13 | } 14 | 15 | @loglocalstorage() 16 | doSomethingElse( a, b ) { 17 | return a + b; 18 | } 19 | 20 | @donotlog() 21 | logSomething(){ 22 | console.log('>>>>>>>>>>>'); 23 | console.error('>>>>>>>>>>>'); 24 | console.warn('>>>>>>>>>>>'); 25 | } 26 | 27 | @donotlogmessages() 28 | logmessage(){ 29 | console.log('>>>>>>>>>>>'); 30 | } 31 | 32 | @donotlogwarnings() 33 | warnSomething(){ 34 | console.warn('>>>>>>>>>>>'); 35 | } 36 | 37 | @donotlogerrors() 38 | errorSomething(){ 39 | console.error('>>>>>>>>>>>'); 40 | } 41 | }; 42 | 43 | describe( 'loggers tests', function () { 44 | let p; 45 | beforeEach( function () { 46 | p = new Person(); 47 | sinon.spy( console, 'log' ); 48 | sinon.spy( console, 'warn' ); 49 | sinon.spy( console, 'error' ); 50 | } ); 51 | 52 | afterEach(function() { 53 | console.log.restore(); 54 | console.warn.restore(); 55 | console.error.restore(); 56 | }); 57 | 58 | // @log 59 | it( '@log: should not affect the function results', function () { 60 | expect( p.doSomething( 1, 2 ) ).to.equal( 3 ); 61 | } ); 62 | 63 | it( '@donotlog: should not call the console methods', function () { 64 | p.logSomething( 1, 2 ) 65 | expect( console.log ).to.not.be.called; 66 | } ); 67 | 68 | it( '@donotlogerrors: should not call the console.error', function () { 69 | p.errorSomething( 1, 2 ) 70 | expect( console.error ).to.not.be.called; 71 | } ); 72 | 73 | it( '@donotlogmessages: should not call the console.log', function () { 74 | p.logmessage( 1, 2 ) 75 | expect( console.log ).to.not.be.called; 76 | } ); 77 | 78 | it( '@donotlogmessages: should not call the console.warn', function () { 79 | p.warnSomething( 1, 2 ) 80 | expect( console.warn ).to.not.be.called; 81 | } ); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | 8 | export { 9 | _immutable as immutable, 10 | _doesNotMutate as doesNotMutate 11 | } from './@immutablors'; 12 | 13 | export { 14 | _acceptsArray as acceptsArray, 15 | _acceptsObject as acceptsObject, 16 | _acceptsInteger as acceptsInteger, 17 | _acceptsNumber as acceptsNumber, 18 | _acceptsBoolean as acceptsBoolean, 19 | _acceptsFunction as acceptsFunction, 20 | _acceptsPromise as acceptsPromise, 21 | _acceptsString as acceptsString, 22 | _validateSchema as validateSchema 23 | } from './@validators'; 24 | 25 | export { _memoization as memoization } from './@memoizator'; 26 | 27 | export { 28 | _debounce as debounce, 29 | _timeout as timeout, 30 | _defer as defer 31 | } from './@timers'; 32 | 33 | export { 34 | _loglocalstorage as loglocalstorage, 35 | _log as log, 36 | _donotlog as donotlog, 37 | _donotlogmessages as donotlogmessages, 38 | _donotlogerrors as donotlogerrors, 39 | _donotlogwarnings as donotlogwarnings 40 | } from './@loggers'; 41 | 42 | export { 43 | _once as once, 44 | _times as times, 45 | _timesCalled as timesCalled 46 | } from './@executors'; 47 | 48 | export { 49 | _readonly as readonly, 50 | _enumerable as enumerable, 51 | _nonenumerable as nonenumerable, 52 | _nonconfigurable as nonconfigurable 53 | } from './@stators'; 54 | 55 | export { 56 | _overridden as overridden, 57 | _forceoverridden as forceoverridden 58 | } from './@inheritedfunctions'; 59 | 60 | export { _trycatch as trycatch } from './@trycatch'; 61 | 62 | export { 63 | _multiInherit as multiInherit, 64 | _multiInherit as multiExtend, 65 | _partialyInherit as partialyInherit, 66 | _partialyInherit as partialyExtend, 67 | _partialyInherit as partiallyInherit, 68 | _partialyInherit as partiallyExtend 69 | } from './@multiinheritance'; 70 | 71 | export { 72 | _passedValuesEqualToNumberOfArguments as passedValuesEqualToNumberOfArguments, 73 | _passedValuesEqualToNumberOfArguments as valuesEqualToNumberOfArguments 74 | } from './@passedValuesEqualToNumberOfArguments'; 75 | 76 | export { _after as after } from './@after'; 77 | 78 | export { _before as before } from './@before'; 79 | 80 | export { 81 | _deprecated as deprecated, 82 | _deprecated as deprecate 83 | } from './@deprecated'; 84 | 85 | export { 86 | _compose as compose, 87 | _compose as rightCompose, 88 | _leftCompose as leftCompose 89 | } from './@compose'; 90 | 91 | export { _autobind as autobind } from './@autobind'; 92 | 93 | export { _abstract as abstract } from './@abstract'; 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | 2016-07-01 Version 0.8.1 4 | ========== 5 | 6 | * Update README 7 | 8 | 2016-07-01 Version 0.8.0 9 | ========== 10 | 11 | * @abstract decorator for classes 12 | * eslint fixes 13 | * dependencies update 14 | 15 | 2016-02-20 Version 0.7.1 16 | ========== 17 | 18 | * Fix depedencies versions in package.json 19 | 20 | 2016-01-16 Version 0.7.0 21 | ========== 22 | 23 | * @autobind decorator for methods 24 | 25 | 2016-01-13 Version 0.6.0 26 | ========== 27 | 28 | * @compose (alias: @rightCompose ), @leftCompose decorators 29 | * @deprecated decorator 30 | * @before decorator 31 | * @after decorator 32 | * @valuesEqualToNumberOfArguments decorator 33 | * Fix failSilent on @acceptsObject 34 | * Internal refactor of multinheritance 35 | * Update README 36 | * Fix typo on export of @partiallyExtend 37 | 38 | 2016-01-10 Version 0.5.0 39 | ========== 40 | 41 | * Started class decorators 42 | * @partialyInherit class decorator 43 | * @multiInherit class decorator 44 | 45 | 46 | 2016-01-09 Version 0.4.1 47 | ========== 48 | 49 | * @trycatch decorator 50 | 51 | 2016-01-08 Version 0.4.0 52 | ========== 53 | 54 | * Update readme 55 | * Remove @configurable decorator, it doesnt make much sense (**breaking change**) 56 | * Fix typo 57 | * @overriden, @forceoverriden decorators 58 | * Update eslint rules 59 | 60 | 61 | 2016-01-08 Version 0.3.2 62 | ========== 63 | 64 | * Update README 65 | * @donotlog, @donotlogerrors, @donotlogmessages, @donotlogwarnings decorators 66 | * Noop helper function 67 | 68 | 69 | 2016-01-08 Version 0.3.1 70 | ========== 71 | 72 | * Fix typo on package.json 73 | * Update Readme 74 | * Helper to validate that isFunction for method decorators 75 | * Fix export alias for @debounce 76 | * @defer decorator 77 | 78 | 79 | 2016-01-07 Version 0.3.0 80 | ========== 81 | 82 | * Update README 83 | * Update package.json 84 | * Eslintignore file 85 | * Accepts a parameter in validation decorators indicating if they will fail silently 86 | * @readonly, @configurable, @nonconfigurable, @enumerable, @nonenumerable decorators 87 | * Small refactor on export 88 | 89 | 90 | 2016-01-06 Version 0.2.2 91 | ========== 92 | 93 | * @timesCalled decorator 94 | * @once and @times decorators 95 | * Fix color on console.log of @loglocalstorage 96 | * Always return the descriptor to be able to chain decorators 97 | * Fix package.json 98 | * Update package.json 99 | * Update README 100 | * npmignore, index entry file, update package 101 | * Small refactor 102 | * @timeout, @debounce decorators 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-decorators", 3 | "homepage": "https://github.com/AvraamMavridis/javascript-decorators", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/AvraamMavridis/javascript-decorators" 7 | }, 8 | "version": "1.1.1", 9 | "main": "./index.js", 10 | "author": "Avraam Mavridis ", 11 | "description": "Decorator Helpers", 12 | "scripts": { 13 | "test": "npm run compile && mocha --compilers js:babel-register --require babel-polyfill", 14 | "compile": "babel --presets es2015,stage-0 --require babel-polyfill -d lib/ src/", 15 | "prepublish": "npm run compile", 16 | "watch": "npm-scripts-watcher", 17 | "lint": "eslint ./src --fix", 18 | "semantic-release": "semantic-release && npm publish && semantic-release post" 19 | }, 20 | "watch": { 21 | "src/**/*.js": [ 22 | "compile" 23 | ] 24 | }, 25 | "keywords": [ 26 | "javascript decorators", 27 | "javascript-decorator", 28 | "javascript decorator", 29 | "javascript-decorators", 30 | "js-decorators", 31 | "core-decorators", 32 | "decorate", 33 | "aspects", 34 | "aop", 35 | "aspect-oriented-programming", 36 | "aspect oriented programming", 37 | "aspects", 38 | "es6", 39 | "es7", 40 | "es2015", 41 | "es2016", 42 | "abstract", 43 | "babel", 44 | "decorators", 45 | "java", 46 | "annotations", 47 | "mixin", 48 | "validateSchema", 49 | "memoization", 50 | "immutable", 51 | "immutability", 52 | "multiple inheritance", 53 | "inheritance", 54 | "extend", 55 | "multiple extend", 56 | "@multiInherit", 57 | "@partiallyInherit", 58 | "@autobind", 59 | "@compose", 60 | "@leftCompose", 61 | "@deprecated", 62 | "@before", 63 | "@after", 64 | "@valuesEqualToNumberOfArguments", 65 | "@overridden", 66 | "@forceoverridden", 67 | "@once", 68 | "@times", 69 | "@timesCalled", 70 | "@trycatch", 71 | "@validateSchema", 72 | "@doesNotMutate", 73 | "@memoization", 74 | "@log", 75 | "@loglocalstorage", 76 | "@donotlog", 77 | "@timeout", 78 | "@defer", 79 | "@enumerable", 80 | "@readony", 81 | "@nonconfigurable", 82 | "@nonenumerable" 83 | ], 84 | "license": "ISC", 85 | "dependencies": { 86 | "deep-equal": "^1.0.1", 87 | "deepcopy": "^1.0.0", 88 | "promise": "^8.0.1" 89 | }, 90 | "devDependencies": { 91 | "babel": "^6.14.0", 92 | "babel-cli": "^6.14.0", 93 | "babel-core": "^6.14.0", 94 | "babel-eslint": "^9.0.0", 95 | "babel-plugin-syntax-object-rest-spread": "^6.13.0", 96 | "babel-plugin-transform-decorators": "^6.13.0", 97 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 98 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 99 | "babel-polyfill": "^6.13.0", 100 | "babel-preset-es2015": "^6.14.0", 101 | "babel-preset-react": "^6.11.1", 102 | "babel-preset-stage-0": "^6.5.0", 103 | "babel-register": "^6.16.3", 104 | "chai": "^4.1.2", 105 | "eslint": "^5.3.0", 106 | "eslint-config-airbnb-base": "^13.0.0", 107 | "eslint-config-avraam": "^1.0.0", 108 | "eslint-plugin-import": "^2.13.0", 109 | "eslint-plugin-jsx-a11y": "^6.1.1", 110 | "eslint-plugin-react": "^7.10.0", 111 | "mocha": "^5.2.0", 112 | "semantic-release": "^15.9.5", 113 | "sinon": "^6.1.4", 114 | "sinon-chai": "^3.2.0" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/@loggers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc, noop } from './helpers'; 8 | 9 | /** 10 | * Logs the passed arguments and the returned value 11 | * 12 | * @method log 13 | * 14 | */ 15 | export const _log = function () { 16 | return function (target, key, descriptor) { 17 | const func = descriptor.value; 18 | descriptorIsFunc(target, func); 19 | descriptor.value = function (...args) { 20 | const res = func.apply(this, args); 21 | console.log('%c Passed Arguments: ', 'background: #222; color: #bada55', args); 22 | console.log('%c Returned Value : ', 'background: #bada55; color: #222', res); 23 | return res; 24 | }; 25 | return descriptor; 26 | }; 27 | }; 28 | 29 | /** 30 | * Returns the global localStorage 31 | * 32 | * @method getLocalStorage 33 | * 34 | * @return { object } 35 | */ 36 | export const _getLocalStorage = function () { 37 | return localStorage; 38 | }; 39 | 40 | /** 41 | * Returns an array with the items on localStorage with their sizes 42 | * 43 | * @method _getLocalStorageItems 44 | * 45 | * @return {[type]} [description] 46 | */ 47 | const _getLocalStorageItems = function () { 48 | const _localStorage = _getLocalStorage(); 49 | let sizes = Object.keys(_localStorage); 50 | sizes = sizes.map(key => { 51 | const obj = {}; 52 | obj.name = key; 53 | obj.size = localStorage[key].length * 2 / 1024 / 1024; 54 | return obj; 55 | }); 56 | return sizes; 57 | }; 58 | 59 | /** 60 | * Returns the total size of the items in the localStorage 61 | * 62 | * @method _getLocalStorageSize 63 | * 64 | * @return { number } 65 | */ 66 | const _getLocalStorageSize = function () { 67 | const items = _getLocalStorageItems(); 68 | const size = items.reduce((sum, next) => sum + next.size, 0); 69 | return size; 70 | }; 71 | 72 | /** 73 | * Logs the localStorage before and after the function call 74 | * 75 | * @method loglocalstorage 76 | * 77 | */ 78 | export const _loglocalstorage = function () { 79 | return function (target, key, descriptor) { 80 | const func = descriptor.value; 81 | descriptorIsFunc(target, func); 82 | descriptor.value = function (...args) { 83 | const sizeBefore = _getLocalStorageSize(); 84 | console.log('%c Local Storage Size Before Function Call: ', 'background: #222; color: #bada55', `${ sizeBefore } MB`); 85 | const res = func.apply(this, args); 86 | const sizeAfter = _getLocalStorageSize(); 87 | console.log('%c Local Storage Size After Function Call : ', 'background: #bada55; color: #222', `${ sizeAfter } MB`); 88 | return res; 89 | }; 90 | return descriptor; 91 | }; 92 | }; 93 | 94 | /** 95 | * donotlog decorator, prevents log statements on the console 96 | * 97 | * @method _donotlog 98 | * 99 | * @return {[type]} [description] 100 | */ 101 | export const _donotbase = function (type) { 102 | const nativeFuncs = {}; 103 | const types = [].concat(type); 104 | 105 | return function (key, target, descriptor) { 106 | const func = descriptor.value; 107 | descriptorIsFunc(key, func); 108 | descriptor.value = function (...args) { 109 | // nooping native console 110 | types.forEach(_type => { 111 | nativeFuncs[_type] = console[_type]; 112 | console[_type] = noop; 113 | }); 114 | 115 | const res = func.apply(this, args); 116 | 117 | // restore native 118 | types.forEach(_type => { 119 | console[_type] = nativeFuncs[_type]; 120 | }); 121 | 122 | return res; 123 | }; 124 | return descriptor; 125 | }; 126 | }; 127 | 128 | export const _donotlogmessages = _donotbase.bind({}, 'log'); 129 | export const _donotlogwarnings = _donotbase.bind({}, 'warn'); 130 | export const _donotlogerrors = _donotbase.bind({}, 'error'); 131 | export const _donotlog = _donotbase.bind({}, [ 'error', 'log', 'warn', 'table' ]); 132 | -------------------------------------------------------------------------------- /src/@validators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validation related decorators 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { 8 | _isObject, 9 | _isArray, 10 | _isNumber, 11 | _isInteger, 12 | _isBoolean, 13 | _isFunction, 14 | _isPromise, 15 | _isValidSchema, 16 | _isString, 17 | _basefunc 18 | } from './validationHelpers'; 19 | 20 | /** 21 | * @acceptsObject Decorator 22 | * 23 | * @method acceptsObject 24 | * 25 | * @param { integer|array } position = 0 Position of the property to validate 26 | * 27 | * @return { function } Decorator 28 | */ 29 | export const _acceptsObject = function acceptsObject(position = 0, failSilent = false) { 30 | return _basefunc(position, _isObject, ' is not an object', failSilent); 31 | }; 32 | 33 | /** 34 | * @acceptsArray Decorator 35 | * 36 | * @method acceptsObject 37 | * 38 | * @param { integer|array } position = 0 Position of the property to validate 39 | * 40 | * @return { function } Decorator 41 | */ 42 | export const _acceptsArray = function acceptsArray(position = 0, failSilent = false) { 43 | return _basefunc(position, _isArray, ' is not an array', failSilent); 44 | }; 45 | 46 | /** 47 | * @acceptsNumber Decorator 48 | * 49 | * @method acceptsObject 50 | * 51 | * @param { integer|array } position = 0 Position of the property to validate 52 | * 53 | * @return { function } Decorator 54 | */ 55 | export const _acceptsNumber = function acceptsNumber(position = 0, failSilent = false) { 56 | return _basefunc(position, _isNumber, ' is not a number', failSilent); 57 | }; 58 | 59 | /** 60 | * @acceptsInteger Decorator 61 | * 62 | * @method acceptsObject 63 | * 64 | * @param { integer|array } position = 0 Position of the property to validate 65 | * 66 | * @return { function } Decorator 67 | */ 68 | export const _acceptsInteger = function acceptsInteger(position = 0, failSilent = false) { 69 | return _basefunc(position, _isInteger, ' is not an integer', failSilent); 70 | }; 71 | 72 | /** 73 | * @acceptsBoolean Decorator 74 | * 75 | * @method acceptsObject 76 | * 77 | * @param { integer|array } position = 0 Position of the property to validate 78 | * 79 | * @return { function } Decorator 80 | */ 81 | export const _acceptsBoolean = function acceptsBoolean(position = 0, failSilent = false) { 82 | return _basefunc(position, _isBoolean, ' is not a boolean', failSilent); 83 | }; 84 | 85 | /** 86 | * @acceptsFunction Decorator 87 | * 88 | * @method acceptsObject 89 | * 90 | * @param { integer|array } position = 0 Position of the property to validate 91 | * 92 | * @return { function } Decorator 93 | */ 94 | export const _acceptsFunction = function acceptsFunction(position = 0, failSilent = false) { 95 | return _basefunc(position, _isFunction, ' is not a function', failSilent); 96 | }; 97 | 98 | /** 99 | * @acceptsPromise Decorator 100 | * 101 | * @method acceptsObject 102 | * 103 | * @param { integer|array } position = 0 Position of the property to validate 104 | * 105 | * @return { function } Decorator 106 | */ 107 | export const _acceptsPromise = function acceptsPromise(position = 0, failSilent = false) { 108 | return _basefunc(position, _isPromise, ' is not a promise', failSilent); 109 | }; 110 | 111 | /** 112 | * @acceptsString Decorator 113 | * 114 | * @method acceptsString 115 | * 116 | * @param { integer|array } position = 0 Position of the property to validate 117 | * 118 | * @return { function } Decorator 119 | */ 120 | export const _acceptsString = function acceptsString(position = 0, failSilent = false) { 121 | return _basefunc(position, _isString, ' is not a string', failSilent); 122 | }; 123 | 124 | /** 125 | * @validateSchema Decorator 126 | * 127 | * @method acceptsObject 128 | * 129 | * @param { object } validation schema 130 | * @param { integer|array } position = 0 Position of the property to validate 131 | * 132 | * @return { function } Decorator 133 | */ 134 | export const _validateSchema = function validateSchema(schema, position = 0, failSilent = false) { 135 | return _isValidSchema(schema, position, failSilent); 136 | }; 137 | -------------------------------------------------------------------------------- /test/validations.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as Promise from 'promise'; 3 | import { acceptsObject, 4 | acceptsArray, 5 | acceptsInteger, 6 | acceptsNumber, 7 | acceptsBoolean, 8 | acceptsFunction, 9 | acceptsPromise, 10 | acceptsString, 11 | validateSchema } from '../src/index.js'; 12 | 13 | class Person { 14 | @acceptsObject() 15 | getAnObject( obj ) { return true; } 16 | 17 | @acceptsArray() 18 | getAnArray( obj ) { return true; } 19 | 20 | @acceptsNumber() 21 | getANumber( obj ) { return true; } 22 | 23 | @acceptsInteger() 24 | getAnInteger( obj ) { return true; } 25 | 26 | @acceptsBoolean([0,1]) 27 | getBoolean( obj, obj2 ) { return true; } 28 | 29 | @acceptsFunction() 30 | getFunction( obj ) { return true; } 31 | 32 | @acceptsPromise() 33 | getPromise( obj ) { return true; } 34 | 35 | @acceptsString() 36 | getString( obj ) { return true; } 37 | 38 | @validateSchema( { test1: 'number', test2: 'object', test3: 'array', test4: 'function', test5: 'promise' } ) 39 | getSchemaValidatedObject( obj ) { return true; } 40 | 41 | @validateSchema( { test1: 'lol', test2: 'object' } ) 42 | getSchemaValidatedObject2( obj ) { return true; } 43 | } 44 | 45 | describe( 'validation tests', function () { 46 | let p; 47 | beforeEach( function () { 48 | p = new Person(); 49 | } ); 50 | 51 | // @acceptsObject 52 | it( '@acceptsObject: execute the function if an object is passed', function () { 53 | expect( p.getAnObject( {} ) ).to.equal( true ); 54 | } ); 55 | 56 | it( '@acceptsObject: throw an Error if a non object is passed', function () { 57 | expect( p.getAnObject.bind( {}, 42 ) ).to.throw( Error ); 58 | } ); 59 | 60 | // @acceptsArray 61 | it( '@acceptsArray: execute the function if an array is passed', function () { 62 | expect( p.getAnArray( [] ) ).to.equal( true ); 63 | } ); 64 | 65 | it( '@acceptsArray: throw an Error if a non array is passed', function () { 66 | expect( p.getAnObject.bind( {}, 42 ) ).to.throw( Error ); 67 | } ); 68 | 69 | // @acceptsNumber 70 | it( '@acceptsNumber: execute the function if a number is passed', function () { 71 | expect( p.getANumber( 42.23213 ) ).to.equal( true ); 72 | } ); 73 | 74 | it( '@acceptsNumber: throw an Error if a non number is passed', function () { 75 | expect( p.getANumber.bind( {}, "something" ) ).to.throw( Error ); 76 | } ); 77 | 78 | // @acceptsInteger 79 | it( '@acceptsInteger: execute the function if an integer is passed', function () { 80 | expect( p.getAnInteger( 42 ) ).to.equal( true ); 81 | } ); 82 | 83 | it( '@acceptsInteger: throw an Error if a non integer is passed', function () { 84 | expect( p.getAnInteger.bind( {}, 42.45 ) ).to.throw( Error ); 85 | } ); 86 | 87 | // @acceptsBoolean 88 | it( '@acceptsBoolean: execute the function if a boolean is passed', function () { 89 | expect( p.getBoolean( true, true ) ).to.equal( true ); 90 | } ); 91 | 92 | it( '@acceptsBoolean: throw an Error if a non boolean is passed', function () { 93 | expect( p.getBoolean.bind( {}, 42.45 ) ).to.throw( Error ); 94 | } ); 95 | 96 | // @acceptsFunction 97 | it( '@acceptsFunction: execute the function if a function is passed', function () { 98 | expect( p.getFunction( function(){} ) ).to.equal( true ); 99 | } ); 100 | 101 | it( '@acceptsFunction: throw an Error if a non boolean is passed', function () { 102 | expect( p.getFunction.bind( {}, 42.45 ) ).to.throw( Error ); 103 | } ); 104 | 105 | // @acceptsPromise 106 | it( '@acceptsPromise: execute the function if a promise is passed', function () { 107 | expect( p.getPromise( Promise.resolve( 42 ) ) ).to.equal( true ); 108 | } ); 109 | 110 | it( '@acceptsPromise: throw an Error if a non promise is passed', function () { 111 | expect( p.getPromise.bind( {}, 42.45 ) ).to.throw( Error ); 112 | } ); 113 | 114 | // @acceptsString 115 | it( '@acceptsacceptsStringPromise: execute the function if a promise is passed', function () { 116 | expect( p.getString( 'paok' ) ).to.equal( true ); 117 | } ); 118 | 119 | it( '@acceptsString: throw an Error if a non promise is passed', function () { 120 | expect( p.getString.bind( {}, 42.45 ) ).to.throw( Error ); 121 | } ); 122 | 123 | // @validateSchema 124 | it( '@validateSchema: execute the function if the object is valid againts the schema', function () { 125 | expect( p.getSchemaValidatedObject( { test1: 2, test2: {}, test3: [1,2,3], test4: function(){}, test5: Promise.resolve( 42 ) } ) ).to.equal( true ); 126 | } ); 127 | 128 | it( '@validateSchema: throw an Error if a non object is passed', function () { 129 | expect( p.getSchemaValidatedObject.bind( {}, 42.45 ) ).to.throw( Error ); 130 | } ); 131 | 132 | it( '@validateSchema: throw an Error if a the object does not have a schema property', function () { 133 | expect( p.getSchemaValidatedObject.bind( {}, { test1: 'something', test3: 'something' } ) ).to.throw( Error ); 134 | } ); 135 | 136 | it( '@validateSchema: throw an Error if a their is an invalid type in the schema definition', function () { 137 | expect( p.getSchemaValidatedObject2.bind( {}, { test1: 'something', test2: 'something' } ) ).to.throw( Error ); 138 | } ); 139 | } ); 140 | -------------------------------------------------------------------------------- /src/validationHelpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validation helpers for simple use cases 3 | * 4 | * @author Avraam Mavridis 5 | * 6 | */ 7 | import { descriptorIsFunc } from './helpers'; 8 | 9 | /** 10 | * Tests if the prop is an Object 11 | * 12 | * @method isObject 13 | * 14 | * @param { any } prop 15 | * 16 | * @return { Boolean } 17 | */ 18 | export const _isObject = function (prop) { 19 | return (typeof prop === 'object') && (prop !== null); 20 | }; 21 | 22 | /** 23 | * Tests if something is an Array 24 | * 25 | * @type { Boolean } 26 | */ 27 | export const _isArray = Array.isArray; 28 | 29 | /** 30 | * Tests if the prop is a number 31 | * 32 | * @method isNumber 33 | * 34 | * @param { any } prop 35 | * 36 | * @return { Boolean } 37 | */ 38 | export const _isNumber = function (prop) { 39 | return typeof prop === 'number' && isFinite(prop); 40 | }; 41 | 42 | /** 43 | * Tests if the prop is an integer 44 | * 45 | * @method inInteger 46 | * 47 | * @param { any } prop 48 | * 49 | * @return { Boolean } 50 | */ 51 | export const _isInteger = function isInteger(prop) { 52 | return _isNumber(prop) && (prop % 1 === 0); 53 | }; 54 | 55 | /** 56 | * Tests if the prop is Boolean 57 | * 58 | * @method isBoolean 59 | * 60 | * @param { any } prop 61 | * 62 | * @return { Boolean } 63 | */ 64 | export const _isBoolean = function (prop) { 65 | return typeof prop === 'boolean'; 66 | }; 67 | 68 | /** 69 | * Tests if the prop is Function 70 | * 71 | * @method isBoolean 72 | * 73 | * @param { any } prop 74 | * 75 | * @return { Boolean } 76 | */ 77 | export const _isFunction = function (prop) { 78 | return typeof prop === 'function'; 79 | }; 80 | 81 | /** 82 | * Tests if the prop is a Promise 83 | * 84 | * @method isPromise 85 | * 86 | * @param { any } prop 87 | * 88 | * @return { Boolean } 89 | */ 90 | export const _isPromise = function (prop) { 91 | return prop !== null 92 | && (typeof prop === 'object' || typeof prop === 'function') 93 | && typeof prop.then === 'function'; 94 | }; 95 | 96 | /** 97 | * Tests if the prop is a String 98 | * 99 | * @method isString 100 | * 101 | * @param { any } prop 102 | * 103 | * @return { Boolean } 104 | */ 105 | export const _isString = function (prop) { 106 | return typeof prop === 'string'; 107 | }; 108 | 109 | /** 110 | * validate a schema property 111 | * 112 | * @method _validateProperty 113 | * 114 | * @param { any } property 115 | * @param { string } type 116 | * 117 | * @return { boolean } or throws exception 118 | */ 119 | const _validateProperty = function (property, type) { 120 | let isValid = true; 121 | switch (type) { 122 | case 'object': 123 | isValid = _isObject(property); 124 | break; 125 | case 'number': 126 | isValid = _isNumber(property); 127 | break; 128 | case 'integer': 129 | isValid = _isInteger(property); 130 | break; 131 | case 'boolean': 132 | isValid = _isBoolean(property); 133 | break; 134 | case 'array': 135 | isValid = _isArray(property); 136 | break; 137 | case 'function': 138 | isValid = _isFunction(property); 139 | break; 140 | case 'string': 141 | isValid = _isString(property); 142 | break; 143 | case 'promise': 144 | isValid = _isPromise(property); 145 | break; 146 | default: 147 | throw Error(`${ type } invalid type`); 148 | } 149 | if (!isValid) { 150 | throw Error(`${ property } is not ${ type }`); 151 | } 152 | }; 153 | 154 | /** 155 | * validate against a schema 156 | * 157 | * @method isValidSchema 158 | * 159 | * @param { object } schema 160 | * @param { number } position = 0 161 | * 162 | * @return { Boolean } 163 | */ 164 | export const _isValidSchema = function (schema, position = 0) { 165 | const schemaKeys = Object.keys(schema); 166 | 167 | return function (target, key, descriptor) { 168 | const func = descriptor.value; 169 | descriptor.value = function (...args) { 170 | const prop = args[position]; 171 | if (!_isObject(prop)) { 172 | throw Error(`${ prop } is not an object`); 173 | } 174 | for (const schemaKey of schemaKeys) { 175 | if (!prop.hasOwnProperty(schemaKey)) { 176 | throw Error(`Object has not "${ schemaKey }" property`); 177 | } 178 | _validateProperty(prop[schemaKey], schema[schemaKey]); 179 | } 180 | return func.apply(this, args); 181 | }; 182 | return descriptor; 183 | }; 184 | }; 185 | 186 | /** 187 | * Returns the positions to validate 188 | * 189 | * @method _getPropsToValidate 190 | * 191 | * @param {[type]} position = 0 [description] 192 | * @param {[type]} args = [] [description] 193 | * 194 | * @return {[type]} [description] 195 | */ 196 | const _getPropsToValidate = function (position = 0, args = []) { 197 | const positions = [].concat(position); 198 | const props = []; 199 | for (const p of positions) { 200 | if (args[p]) { 201 | props.push(args[p]); 202 | } 203 | } 204 | return props; 205 | }; 206 | 207 | /** 208 | * Base decorator function for validation 209 | * 210 | * @method _basefunc 211 | * 212 | * @param { integer } position Position of the property to validate 213 | * @param { function } validationFunc Validation function 214 | * @param { string } errorMsg Error message in case of invalid 215 | * 216 | * @return { function } decorator function 217 | */ 218 | export const _basefunc = function (position = 0, validationFunc, errorMsg, failSilent) { 219 | return function (key, target, descriptor) { 220 | const func = descriptor.value; 221 | descriptorIsFunc(key, func); 222 | descriptor.value = function (...args) { 223 | const props = _getPropsToValidate(position, args); 224 | props.forEach(prop => { 225 | if (!validationFunc(prop)) { 226 | if (failSilent) return; 227 | throw Error(`${ prop } ${ errorMsg }`); 228 | } 229 | }); 230 | return func.apply(this, args); 231 | }; 232 | 233 | return descriptor; 234 | }; 235 | }; 236 | -------------------------------------------------------------------------------- /EXAMPLES.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | ### @multiInherit 4 | 5 | ```js 6 | class Manager{ 7 | render(){ return 42; } 8 | init(){ return 43; } 9 | t(){ return 44; } 10 | }; 11 | class Employee{ 12 | render(){ return 45; } 13 | mount(){ return 46; } 14 | t(){ return 47;} 15 | }; 16 | 17 | @multiInherit( Manager, Employee ) 18 | class Person{ 19 | t(){ return 3; } 20 | }; 21 | 22 | const p = new Person(); 23 | p.render(); // 45 24 | p.t(); // 3 25 | p.init(); // 43 26 | ``` 27 | 28 | ### @partiallyInherit 29 | 30 | ```js 31 | class Manager{ 32 | render(){ return 42; } 33 | init(){ return 43; } 34 | t(){ return 44; } 35 | }; 36 | 37 | @partiallyInherit( Manager, ['render', 't'] ) 38 | class Person{ 39 | t(){ return 3; } 40 | }; 41 | 42 | const p = new Person(); 43 | p.render(); // 42 44 | p.t(); // 3 45 | p.init(); // throws error, init undefined 46 | ``` 47 | 48 | ### @compose 49 | 50 | ```js 51 | 52 | const r = (t) => t * 5; 53 | const r2 = (t) => t + 3; 54 | 55 | class Person { 56 | @compose([r, r2]) 57 | doSomething() { 58 | return 2; 59 | } 60 | 61 | @leftCompose([r, r2]) 62 | doSomething2() { 63 | return 2; 64 | } 65 | }; 66 | 67 | 68 | var p = new Person(); 69 | p.doSomething(); // 13 70 | p.doSomething2(); // 25 71 | ``` 72 | 73 | ### @valuesEqualToNumberOfArguments 74 | 75 | ```js 76 | class Person { 77 | @valuesEqualToNumberOfArguments() 78 | doSomething( a ) { 79 | return a; 80 | } 81 | }; 82 | 83 | 84 | var p = new Person(); 85 | p.doSomething( 1,2,3,4,5,5,6); // throws an exception 86 | ``` 87 | 88 | ### @validateSchema 89 | 90 | Executes the method only if the passed values are valid according to the provided **schema**. Example: 91 | 92 | ```js 93 | 94 | class Person{ 95 | @validateSchema( { age: 'number', childs: 'object', jobs: 'array', name: 'string' } ); 96 | setPersonDetails( obj ) { ... } 97 | } 98 | 99 | ``` 100 | 101 | ### @acceptsObject 102 | 103 | Executes the method only if the passed value is an object otherwise throws an exception, accepts position (default=0) or array of positions. Example: 104 | 105 | ```js 106 | 107 | class Person{ 108 | @acceptsObject( [0,2], true ); 109 | doSomething( obj, str, obj ) { ... } 110 | } 111 | 112 | ``` 113 | 114 | ### @immutable 115 | 116 | Makes a deepcopy of the passed arguments and executes the method with the copy to ensure that the initial parameters are not mutated. Example: 117 | 118 | ```js 119 | 120 | class Person{ 121 | @immutable(); 122 | doSomething( arg1 ) { 123 | arg1.test = 10; 124 | } 125 | } 126 | 127 | var obj = { test: 5 }; 128 | var p = new Person(); 129 | p.dosomething( obj ); 130 | console.log( obj.test ); // 5; 131 | ``` 132 | 133 | ### @doesNotMutate 134 | 135 | Executes the method only if it doesnt mutate the passed arguments. Useful when the class extends another class and/or calls methods from the parent. Example: 136 | 137 | ```js 138 | 139 | class Person{ 140 | @doesNotMutate(); 141 | doSomething( arg1 ) { 142 | arg1.test = 10; 143 | } 144 | } 145 | 146 | var obj = { test: 5 }; 147 | var p = new Person(); 148 | p.dosomething( obj ); // throws an exception because doSomething mutates the passed object. 149 | ``` 150 | 151 | ### @memoization 152 | 153 | ```js 154 | 155 | class Person{ 156 | @memoization(); 157 | doSomethingTimeConsuming( arg1 ) { 158 | arg1.test = 10; 159 | } 160 | } 161 | 162 | var obj = { test: 5 }; 163 | var p = new Person(); 164 | p.doSomethingTimeConsuming( obj ); // calls the function 165 | p.doSomethingTimeConsuming( obj ); // immediately returns the result without calling the function 166 | ``` 167 | 168 | ### @log 169 | 170 | ```js 171 | 172 | class Person{ 173 | @log(); 174 | doSomething(...args){ 175 | return args.join('-'); 176 | } 177 | } 178 | 179 | var p = new Person(); 180 | p.doSomething( 2 , 3 ); 181 | ``` 182 | Will log in the console: 183 | 184 | ![Logged on console](http://oi64.tinypic.com/120ta1c.jpg "Logged on console") 185 | 186 | 187 | ### @loglocalstorage 188 | 189 | ```js 190 | 191 | class Person{ 192 | @loglocalstorage() 193 | doSomething(){ 194 | localStorage.setItem('somethingonlocalstorage', "Lorem ipsum dol'or sit amet, consectetur adipiscing elit. Morbi congue urna id enim lobortis placerat. Integer commodo nulla in ipsum pharetra, ac malesuada tellus viverra. Integer aliquam semper nisi vehicula lobortis. Ut vel felis nec sem sollicitudin eleifend. Ut sem magna, vehicula in dui in, sodales ultrices arcu. Aliquam suscipit sagittis aliquet. Phasellus convallis faucibus massa, quis tincidunt nisl accumsan sed.") 195 | } 196 | } 197 | 198 | var p = new Person(); 199 | p.doSomething( ); 200 | ``` 201 | Will log in the console something like: 202 | 203 | ![Logged on console](http://oi68.tinypic.com/n33tzo.jpg "Logged on console") 204 | 205 | 206 | ### @timeout (alias: @debounce) 207 | 208 | ```js 209 | 210 | class Person{ 211 | @timeout( 2000 ); 212 | doSomething(){ 213 | ... 214 | } 215 | } 216 | 217 | var p = new Person(); 218 | p.doSomething(); // Executed after 2secs 219 | ``` 220 | 221 | 222 | ### @once() 223 | 224 | ```js 225 | 226 | class Person{ 227 | @once(); 228 | doSomething(a, b){ 229 | return a + b; 230 | } 231 | } 232 | 233 | var p = new Person(); 234 | p.doSomething(1,2); // returns 3 235 | p.doSomething(21,2); // keeps returning 3 236 | p.doSomething(14,42); // keeps returning 3 237 | ``` 238 | 239 | ### @times(n) 240 | 241 | ```js 242 | 243 | class Person{ 244 | @times(2); 245 | doSomething(a, b){ 246 | return a + b; 247 | } 248 | } 249 | 250 | var p = new Person(); 251 | p.doSomething(1,2); // returns 3 252 | p.doSomething(21,2); // returns 23 253 | p.doSomething(14,42); // keeps returning 23 254 | p.doSomething(14,542); // keeps returning 23 255 | ``` 256 | 257 | ### @timesCalled() 258 | 259 | ```js 260 | 261 | class Person{ 262 | @timesCalled(); 263 | doSomething(a, b){ 264 | return a + b; 265 | } 266 | } 267 | 268 | var p = new Person(); 269 | p.doSomething(1,2); 270 | p.doSomething(21,2); 271 | p.doSomething(14,42); 272 | console.log( p.doSomething.timesCalled ); // 3 273 | ``` 274 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # javascript-decorators 2 | 3 | Compatible with Node JS version >= 5 4 | 5 | [![npm version](https://badge.fury.io/js/javascript-decorators.svg)](https://badge.fury.io/js/javascript-decorators) 6 | 7 | [![Build Status](https://travis-ci.org/AvraamMavridis/javascript-decorators.svg?branch=master)](https://travis-ci.org/AvraamMavridis/javascript-decorators) 8 | 9 | [![Greenkeeper badge](https://badges.greenkeeper.io/AvraamMavridis/javascript-decorators.svg)](https://greenkeeper.io/) 10 | 11 | Common helpers using es7 decorators. 12 | 13 | You can see the [Changelog](https://github.com/AvraamMavridis/javascript-decorators/blob/master/CHANGELOG.md) for the version specific changes. 14 | 15 | This project adheres to [Semantic Versioning](http://semver.org/). 16 | 17 | 18 | ## How to use 19 | 20 | [![NPM](https://nodei.co/npm/javascript-decorators.png?mini=true)](https://nodei.co/npm/javascript-decorators/) 21 | 22 | `npm i --save javascript-decorators` 23 | 24 | And then to use a decorator just import it: 25 | 26 | ```js 27 | import { multiInherit } from 'javascript-decorators' 28 | 29 | class Manager{ 30 | render(){ return 42; } 31 | init(){ return 43; } 32 | t(){ return 44; } 33 | }; 34 | class Employee{ 35 | render(){ return 45; } 36 | mount(){ return 46; } 37 | t(){ return 47;} 38 | }; 39 | 40 | @multiInherit( Manager, Employee ) 41 | class Person{ 42 | t(){ return 3; } 43 | }; 44 | 45 | const p = new Person(); 46 | p.render(); // 45 47 | p.t(); // 3 48 | p.init(); // 43 49 | ``` 50 | 51 | **More examples [here](https://github.com/AvraamMavridis/javascript-decorators/blob/master/EXAMPLES.md)** 52 | 53 | ## Decorators 54 | 55 | #### Class decorators: 56 | 57 | + **@abstract()** : Make a class "abstract". It cannot be instantiated directly, but it can be inherrited. 58 | 59 | + **@multiInherit( array of classes )** (alias: `@multiExtend()`) : Inherit all the methods of the passed classes. If two classes have method with the same name the last one is inheritted. 60 | 61 | + **@partiallyInherit( class or array of classes, methodName or array of methodNames )** (alias: `@partiallyExtend()`) : Inherit only the specified methods. 62 | 63 | #### Method decorators: 64 | 65 | + **@autobind**: function calls always have this refer to the class instance 66 | 67 | + **@compose( methods ) (alias: @rightCompose)**: Call each method passing the result of the previous as an argument. In mathematics `(g º f)(x) = g(f(x))`. The functions are executed from right-to-left with the class's method executed last. See more on examples. 68 | 69 | + **@leftCompose( methods )**: The left-to-right version of the previous. 70 | 71 | + **@deprecated**: Logs a warning message indicating that the method has been deprecated. 72 | 73 | + **@before ( beforeFunc )**: Executes the `beforeFunc` before the class" method. 74 | 75 | + **@after ( afterFunc )**: Executes the `afterFunc` after the class" method. 76 | 77 | + **@valuesEqualToNumberOfArguments ( failSilent )**: Executes the method only if the number of values pass to the function is equal with the number of function's parameters, otherwise it will through an exception unless failSilent = true. 78 | 79 | + **@overridden()** The method can be called from an instance of the base class, but cannot be called from an instance of a derived class. The derived class has to define its own version. 80 | 81 | + **@forceoverridden()** This is a more strict version of the previous. The method cannot be called from an instance of the base class or derived class. The derived class has to define its own version. Not particularly useful, mostly as a signal that the method has to be implemented on the subclass. 82 | 83 | + **@once()** : Executes the function only once. Repeat calls return the value of the first call. 84 | 85 | + **@times(n)** : Executes the function at most `n` times. Repeat calls return the value of the **nth** call. 86 | *n: number* 87 | 88 | + **@timesCalled()** : Attaches a `timesCalled` property to the function that indicates how many times the function has been called. 89 | 90 | + **@trycatch( errorHandler )** : Wraps the function around a try/catch, requires a passed errorHandler function 91 | 92 | + **@validateSchema( schema )** : Executes the function only if the schema is valid, 93 | *schema: object* 94 | 95 | + **@acceptsObject( positions, failSilent )** : Executes the function only if the passed arg is an object 96 | *positions: number|array (optional, default 0), failSilent: boolean(optional, default false)* 97 | 98 | + **@acceptsArray( positions, failSilent )** : Executes the function only if the passed arg is an array 99 | 100 | + **@acceptsInteger( positions, failSilent )**: Executes the function only if the passed arg is an integer 101 | 102 | + **@acceptsNumber( positions, failSilent )**: Executes the function only if the passed arg is a number 103 | 104 | + **@acceptsBoolean( positions, failSilent )**: Executes the function only if the passed arg is a boolean 105 | 106 | + **@acceptsFunction( positions, failSilent )**: Executes the function only if the passed arg is a function 107 | 108 | + **@acceptsString( positions, failSilent )**: Executes the function only if the passed arg is a string 109 | 110 | + **@acceptsPromise( positions, failSilent )**: Executes the function only if the passed arg is a promise 111 | 112 | + **@immutable()**: Makes a deepcopy of the passed arguments and executes the method with the copy to ensure that the initial parameters are not mutated 113 | 114 | + **@doesNotMutate()** : Executes the method only if it doesnt mutate the passed arguments. Useful when the class extends another class and/or calls methods from the parent. 115 | 116 | + **@memoization()** : Use the [memoization](https://en.wikipedia.org/wiki/Memoization) technique to prevent expensive function calls. 117 | 118 | + **@log()**: Logs the passed values and the returned result. 119 | 120 | + **@loglocalstorage()** : Logs the size of the localstorage before and after the function call. 121 | 122 | + **@donotlog()** : Do not log messages, errors and warnings. 123 | 124 | + **@donotlogmessages()** : Do not log messages. 125 | 126 | + **@donotlogwarnings()** : Do not log warnings. 127 | 128 | + **@donotlogerrors()** : Do not log errors. 129 | 130 | + **@timeout( ms )** (alias: `@debounce( ms )`) : Executes the function after the specified wait (default 300ms) 131 | *ms: number* 132 | 133 | + **@defer()** : Executes the function when the current call stack has cleared 134 | 135 | + **@enumerable()** 136 | 137 | #### Method & Property decorators: 138 | 139 | + **@readony()** 140 | 141 | + **@nonconfigurable()** 142 | 143 | #### Property decorators: 144 | 145 | + **@nonenumerable()** 146 | 147 | 148 | ## Contributing 149 | Feel free to open issues, make suggestions or send PRs. 150 | This project adheres to the Contributor Covenant [code of conduct](http://contributor-covenant.org/). By participating, you are expected to uphold this code. 151 | 152 | Contact: 153 | 154 | Twitter: [@avraamakis](https://twitter.com/avraamakis) 155 | --------------------------------------------------------------------------------