├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.js ├── package.json ├── src ├── index.js └── mixin.js └── test ├── .jshintrc ├── setup ├── node.js └── setup.js └── unit └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true; 4 | 5 | [*] 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: https://goel.io/joe 2 | 3 | #####=== Node ===##### 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 31 | node_modules 32 | 33 | # Debug log from npm 34 | npm-debug.log 35 | 36 | #####=== Linux ===##### 37 | *~ 38 | 39 | # KDE directory preferences 40 | .directory 41 | 42 | # Linux trash folder which might appear on any partition or disk 43 | .Trash-* 44 | 45 | #####=== OSX ===##### 46 | .DS_Store 47 | .AppleDouble 48 | .LSOverride 49 | 50 | # Icon must end with two \r 51 | Icon 52 | 53 | # Thumbnails 54 | ._* 55 | 56 | # Files that might appear on external disk 57 | .Spotlight-V100 58 | .Trashes 59 | 60 | # Directories potentially created on remote AFP share 61 | .AppleDB 62 | .AppleDesktop 63 | Network Trash Folder 64 | Temporary Items 65 | .apdisk 66 | 67 | #####=== Windows ===##### 68 | # Windows image file caches 69 | Thumbs.db 70 | ehthumbs.db 71 | 72 | # Folder config file 73 | Desktop.ini 74 | 75 | # Recycle Bin used on file shares 76 | $RECYCLE.BIN/ 77 | 78 | # Windows Installer files 79 | *.cab 80 | *.msi 81 | *.msm 82 | *.msp 83 | 84 | # Windows shortcuts 85 | *.lnk 86 | 87 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "esnext": true 4 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "eqeqeq" : true, 5 | "forin" : false, 6 | "immed" : true, 7 | "indent" : 2, 8 | "latedef" : true, 9 | "newcap" : true, 10 | "noarg" : true, 11 | "nonbsp" : true, 12 | "nonew" : true, 13 | "plusplus" : false, 14 | "undef" : true, 15 | "unused" : true, 16 | "strict" : false, 17 | "maxparams" : 4, 18 | "maxdepth" : 2, 19 | "maxstatements" : 15, 20 | "maxcomplexity" : 10, 21 | "maxlen" : 100, 22 | 23 | "asi" : false, 24 | "boss" : false, 25 | "debug" : false, 26 | "eqnull" : false, 27 | "esnext" : true, 28 | "evil" : false, 29 | "expr" : false, 30 | "funcscope" : false, 31 | "globalstrict" : false, 32 | "iterator" : false, 33 | "lastsemic" : false, 34 | "loopfunc" : false, 35 | "maxerr" : 50, 36 | "notypeof" : false, 37 | "proto" : false, 38 | "scripturl" : false, 39 | "shadow" : false, 40 | "supernew" : false, 41 | "validthis" : false, 42 | "noyield" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : false, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "worker" : false, 55 | "wsh" : false, 56 | "yui" : false 57 | } 58 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "io.js" 6 | sudo: false 7 | script: "gulp coverage" 8 | after_success: 9 | - npm install -g codeclimate-test-reporter 10 | - codeclimate < coverage/lcov.info 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Graeme Yeates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lodash-decorators 2 | 3 | As [the ES7 decorators proposal](https://github.com/wycats/javascript-decorators) [start to gain popularity and creep into JS transpilers](https://twitter.com/sebmck/status/579300313514950657), it seems like a great time to implement some of the most useful ones provided by `lodash` and `underscore`. As currently no environment or transpiler fully supports generators yet, the code in this repo is hypothetical. [`Babel` is planning to release `decorators` soon](https://twitter.com/sebmck/status/579482622998409217) (updates to follow). 4 | 5 | I've made the most useful set (for this case) of the wrapping functions `lodash` available as method decorators as shown below. 6 | 7 | #### Example 8 | ```js 9 | import { 10 | after, autobind, before, curry, 11 | curryRight, debounce, flow, flowRight, 12 | memoize, negate, once, throttle 13 | } from 'lodash-decorators'; 14 | import _ from 'lodash'; 15 | 16 | class DecoratedClass { 17 | @after(3) 18 | after3() { 19 | this.notifier('After has been called'); 20 | } 21 | 22 | @before(3) 23 | before3() { 24 | this.notifier('Before has been called'); 25 | } 26 | 27 | @autobind 28 | @curry 29 | curry(a, b, c) { 30 | this.notifier('Curry called with ', a, b, c); 31 | } 32 | 33 | @autobind 34 | @curryRight 35 | curryRight(a, b, c) { 36 | this.notifier('curryRight called with ', a, b, c); 37 | } 38 | 39 | @debounce(1000) 40 | debounce() { 41 | this.notifier('Debounce called'); 42 | } 43 | 44 | @flow(_.isString) 45 | get composed() { 46 | return Math.random() > 0.5 ? 'string' : null; 47 | } 48 | 49 | @flowRight(_.isString) 50 | set compose(isArgString) { 51 | this.notifier('Compose called with string:', isArgString); 52 | } 53 | 54 | @memoize 55 | memoize(a, b) { 56 | this.notifier('Memoize called with', a, b); 57 | } 58 | 59 | @negate 60 | get negate() { 61 | this.notifier('Getter negate was called'); 62 | return false; 63 | } 64 | 65 | @once 66 | get once() { 67 | this.notifier('Get once was called'); 68 | } 69 | 70 | @once 71 | set once(a) { 72 | this.notifier('Set once was called', a); 73 | } 74 | 75 | @throttle(1000, {leading: false}) 76 | throttle() { 77 | this.notifier('Throttle was called'); 78 | } 79 | 80 | notifier() { 81 | console.log(arguments); 82 | } 83 | } 84 | ``` -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const $ = require('gulp-load-plugins')(); 3 | const del = require('del'); 4 | const path = require('path'); 5 | const mkdirp = require('mkdirp'); 6 | const isparta = require('isparta'); 7 | 8 | const manifest = require('./package.json'); 9 | const config = manifest.nodeBoilerplateOptions; 10 | const mainFile = manifest.main; 11 | const destinationFolder = path.dirname(mainFile); 12 | 13 | // Remove the built files 14 | gulp.task('clean', function(cb) { 15 | del([destinationFolder], cb); 16 | }); 17 | 18 | // Send a notification when JSHint fails, 19 | // so that you know your changes didn't build 20 | function jshintNotify(file) { 21 | if (!file.jshint) { return; } 22 | return file.jshint.success ? false : 'JSHint failed'; 23 | } 24 | 25 | function jscsNotify(file) { 26 | if (!file.jscs) { return; } 27 | return file.jscs.success ? false : 'JSCS failed'; 28 | } 29 | 30 | // Lint our source code 31 | gulp.task('lint-src', function() { 32 | return gulp.src(['src/**/*.js']) 33 | .pipe($.plumber()) 34 | .pipe($.jshint()) 35 | .pipe($.jshint.reporter('jshint-stylish')) 36 | .pipe($.notify(jshintNotify)) 37 | .pipe($.jscs()) 38 | .pipe($.notify(jscsNotify)) 39 | .pipe($.jshint.reporter('fail')); 40 | }); 41 | 42 | // Lint our test code 43 | gulp.task('lint-test', function() { 44 | return gulp.src(['test/**/*.js']) 45 | .pipe($.plumber()) 46 | .pipe($.jshint()) 47 | .pipe($.jshint.reporter('jshint-stylish')) 48 | .pipe($.notify(jshintNotify)) 49 | .pipe($.jscs()) 50 | .pipe($.notify(jscsNotify)) 51 | .pipe($.jshint.reporter('fail')); 52 | }); 53 | 54 | // Build two versions of the library 55 | gulp.task('build', ['lint-src', 'clean'], function() { 56 | 57 | // Create our output directory 58 | mkdirp.sync(destinationFolder); 59 | return gulp.src('src/**/*.js') 60 | .pipe($.plumber()) 61 | .pipe($.babel({ blacklist: ['useStrict'] })) 62 | .pipe(gulp.dest(destinationFolder)); 63 | }); 64 | 65 | function test() { 66 | return gulp.src(['test/setup/node.js', 'test/unit/**/*.js'], {read: false}) 67 | .pipe($.plumber()) 68 | .pipe($.mocha({reporter: 'dot', globals: config.mochaGlobals})); 69 | } 70 | 71 | // Make babel preprocess the scripts the user tries to import from here on. 72 | require('babel/register'); 73 | 74 | gulp.task('coverage', function(done) { 75 | gulp.src(['src/*.js']) 76 | .pipe($.plumber()) 77 | .pipe($.istanbul({ instrumenter: isparta.Instrumenter })) 78 | .pipe($.istanbul.hookRequire()) 79 | .on('finish', function() { 80 | return test() 81 | .pipe($.istanbul.writeReports()) 82 | .on('end', done); 83 | }); 84 | }); 85 | 86 | 87 | // Lint and run our tests 88 | gulp.task('test', ['lint-src', 'lint-test'], test); 89 | 90 | // Run the headless unit tests as you make changes. 91 | gulp.task('watch', ['test'], function() { 92 | gulp.watch(['src/**/*', 'test/**/*', 'package.json', '**/.jshintrc', '.jscsrc'], ['test']); 93 | }); 94 | 95 | // An alias of test 96 | gulp.task('default', ['test']); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-decorators", 3 | "version": "0.0.1", 4 | "description": "A set of ES7 decorators from lodash", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "gulp", 8 | "build": "gulp build", 9 | "coverage": "gulp coverage", 10 | "prepublish": "npm run build" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/megawac/lodash-decorators.git" 15 | }, 16 | "keywords": [], 17 | "author": "Graeme Yeates ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/megawac/lodash-decorators/issues" 21 | }, 22 | "homepage": "https://github.com/megawac/lodash-decorators", 23 | "devDependencies": { 24 | "babel": "^4.3.0", 25 | "chai": "^2.0.0", 26 | "del": "^1.1.1", 27 | "gulp": "^3.8.10", 28 | "gulp-babel": "^4.0.0", 29 | "gulp-istanbul": "^0.6.0", 30 | "gulp-jscs": "^1.4.0", 31 | "gulp-jshint": "^1.9.0", 32 | "gulp-load-plugins": "^0.8.0", 33 | "gulp-mocha": "^2.0.0", 34 | "gulp-notify": "^2.1.0", 35 | "gulp-plumber": "^0.6.6", 36 | "isparta": "^2.0.0", 37 | "jshint-stylish": "^1.0.0", 38 | "mkdirp": "^0.5.0", 39 | "mocha": "^2.1.0", 40 | "sinon": "^1.12.2", 41 | "sinon-chai": "^2.7.0" 42 | }, 43 | "nodeBoilerplateOptions": { 44 | "mochaGlobals": [ 45 | "stub", 46 | "spy", 47 | "expect" 48 | ] 49 | }, 50 | "dependencies": { 51 | "lodash": "^3.5.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import lodash from 'lodash/function'; 2 | import mixin from './mixin'; 3 | 4 | export default mixin(lodash); 5 | -------------------------------------------------------------------------------- /src/mixin.js: -------------------------------------------------------------------------------- 1 | import has from 'lodash/object/has'; 2 | import each from 'lodash/internal/arrayEach'; 3 | 4 | let methodDecorators = [ 5 | 'after', 'before', 'curry', 'compose', 'curryRight', 'debounce', 6 | 'flow', 'flowRight', 'memoize', 'negate', 'once', 'spread', 'throttle' 7 | ]; 8 | 9 | export function createMethodDecorator(decoratorFunc) { 10 | return function wrapDecorator(...args) { 11 | return function decorator(target, name, descriptor) { 12 | let {get, set, value} = descriptor; 13 | if (typeof get === "function") { 14 | descriptor.get = decoratorFunc(get, ...args); 15 | } 16 | else if (typeof set === "function") { 17 | descriptor.set = decoratorFunc(set, ...args); 18 | } 19 | else if (typeof value === "function") { 20 | descriptor.value = decoratorFunc(value, ...args); 21 | } 22 | return descriptor; 23 | }; 24 | }; 25 | } 26 | 27 | export default function mixin(_instance) { 28 | let decorators = {}; 29 | 30 | let {bind} = _instance; 31 | if (typeof bind === 'function') { 32 | decorators.autobind = createMethodDecorator(fn => function autobind() { 33 | return bind(fn, this); 34 | }); 35 | } 36 | 37 | each(methodDecorators, (method) => { 38 | if (has(_instance, method)) { 39 | decorators[method] = createMethodDecorator(_instance[method]); 40 | } 41 | }); 42 | 43 | return decorators; 44 | } 45 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "eqeqeq" : true, 5 | "forin" : false, 6 | "immed" : true, 7 | "indent" : 2, 8 | "latedef" : true, 9 | "newcap" : true, 10 | "noarg" : true, 11 | "nonbsp" : true, 12 | "nonew" : true, 13 | "plusplus" : false, 14 | "undef" : true, 15 | "unused" : true, 16 | "strict" : false, 17 | "maxparams" : 4, 18 | "maxdepth" : 2, 19 | "maxstatements" : 15, 20 | "maxcomplexity" : 10, 21 | "maxlen" : 200, 22 | 23 | "asi" : false, 24 | "boss" : false, 25 | "debug" : false, 26 | "eqnull" : false, 27 | "esnext" : true, 28 | "evil" : false, 29 | "expr" : true, 30 | "funcscope" : false, 31 | "globalstrict" : false, 32 | "iterator" : false, 33 | "lastsemic" : false, 34 | "loopfunc" : false, 35 | "maxerr" : 50, 36 | "notypeof" : false, 37 | "proto" : false, 38 | "scripturl" : false, 39 | "shadow" : false, 40 | "supernew" : false, 41 | "validthis" : false, 42 | "noyield" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : true, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "worker" : false, 55 | "wsh" : false, 56 | "yui" : false, 57 | "globals": { 58 | "MyLibrary": true, 59 | "console": true, 60 | "sinon": true, 61 | "spy": true, 62 | "stub": true, 63 | "describe": true, 64 | "before": true, 65 | "after": true, 66 | "beforeEach": true, 67 | "afterEach": true, 68 | "it": true, 69 | "expect": true 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/setup/node.js: -------------------------------------------------------------------------------- 1 | global.chai = require('chai'); 2 | global.sinon = require('sinon'); 3 | global.chai.use(require('sinon-chai')); 4 | 5 | require('babel/register'); 6 | require('./setup')(); 7 | -------------------------------------------------------------------------------- /test/setup/setup.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | global.expect = global.chai.expect; 3 | 4 | beforeEach(function() { 5 | this.sandbox = global.sinon.sandbox.create(); 6 | global.stub = this.sandbox.stub.bind(this.sandbox); 7 | global.spy = this.sandbox.spy.bind(this.sandbox); 8 | }); 9 | 10 | afterEach(function() { 11 | delete global.stub; 12 | delete global.spy; 13 | this.sandbox.restore(); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import {after, before, curry, curryRight, debounce, memoize, negate, once, throttle} from '../../src/index'; 2 | 3 | class DecoratedClass { 4 | 5 | @after(3) 6 | after3() { 7 | this.notifier('After has been called'); 8 | } 9 | 10 | @before(3) 11 | before3() { 12 | this.notifier('Before has been called'); 13 | } 14 | 15 | @curry 16 | curry(a, b, c) { 17 | this.notifier('Curry called with ', a, b, c); 18 | } 19 | 20 | @curryRight 21 | curryRight(a, b, c) { 22 | this.notifier('curryRight called with ', a, b, c); 23 | } 24 | 25 | @debounce(1000) 26 | debounce() { 27 | this.notifier('Debounce called'); 28 | } 29 | 30 | @memoize 31 | memoize(a, b) { 32 | this.notifier('Memoize called with', a, b); 33 | } 34 | 35 | @negate 36 | get negate() { 37 | this.notifier('Getter negate was called'); 38 | return false; 39 | } 40 | 41 | @once 42 | get once() { 43 | this.notifier('Get once was called'); 44 | } 45 | 46 | @once 47 | set once() { 48 | this.notifier('Set once was called'); 49 | } 50 | 51 | @throttle(1000, {leading: false}) 52 | throttle() { 53 | this.notifier('Throttle was called'); 54 | } 55 | 56 | notifier() { 57 | console.log(arguments); 58 | } 59 | }; 60 | 61 | describe('A feature test', () => { 62 | // Todo 63 | }); 64 | --------------------------------------------------------------------------------