├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── gulpfile.js ├── overload.js ├── overload.min.js ├── overload.min.js.gz ├── package.json └── test ├── index.html ├── node.js └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.exe 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true 14 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Joseph Clay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | overload-js 2 | ========== 3 | 4 | Function overloading in JavaScript for 3.9KB minified, 1.4KB gzipped. 5 | 6 | Provides tools to mimic [function overloading][1] that is present in most strictly-types languages. Prevents messy, long, if-statement, type-checking functions that are hard to read and maintain. Style and API inspired by [Moreiki][2] and [Mongoose][3]. 7 | 8 | [1]: 9 | [2]: 10 | [3]: 11 | 12 | `npm install overload-js` 13 | 14 | Basic Usage 15 | ----------- 16 | 17 | ```javascript 18 | var hello = (function() { 19 | 20 | var secret = '!'; 21 | 22 | return overload() 23 | .args().use(function() { 24 | return secret; 25 | }) 26 | .args(String).use(function(val) { 27 | secret = val; 28 | }); 29 | 30 | }()); 31 | 32 | hello('world'); // calls setter 33 | hello(); // returns 'world' 34 | hello(0); // throws a Type Error 35 | ``` 36 | 37 | Detectable types 38 | ---------------- 39 | 40 | ```javascript 41 | null 42 | undefined 43 | Infinity 44 | Date 45 | NaN 46 | Number 47 | String 48 | Object 49 | Array 50 | RegExp 51 | Boolean 52 | Function 53 | Element // browser only 54 | ``` 55 | 56 | Overload with a map 57 | ---------------- 58 | 59 | A map can be defined as an overload as well: 60 | 61 | ```javascript 62 | var hello = overload.map({ 63 | what: String 64 | }).use(function(obj) { 65 | return 'hello ' + obj.what; 66 | }); 67 | 68 | hello({ what: 'world!' }); // returns 'hello world!' 69 | hello('world'); // throws a Type Error 70 | ``` 71 | 72 | or a map can be used as a custom type (see below): 73 | ```javascript 74 | var hello = overload.args(String, o.map({ 75 | what: String 76 | })).use(function(str, obj) { 77 | return str + obj.what; 78 | }); 79 | 80 | hello('hello', { what: 'world!' }); // returns 'hello world!' 81 | hello('hello', 'world'); // throws a Type Error 82 | ``` 83 | 84 | Custom types 85 | ---------------- 86 | 87 | A custom type can be defined by passing a `string` and validation `function` to 88 | `defineType`. The validation function will be passed the value to validate 89 | and expects a `boolean` return. 90 | 91 | ```javascript 92 | overload.defineType('$', function(val) { 93 | return val instanceof jQuery; 94 | }); 95 | ``` 96 | 97 | Custom types are available under `o`. 98 | 99 | ```javascript 100 | var overload = require('overload-js'), 101 | o = overload.o; 102 | var method = overload().args(o.$).use(function($elem) { 103 | console.log($elem); 104 | }); 105 | 106 | method(); // fails 107 | method(''); // fails 108 | method($('body')); // succeeds 109 | ``` 110 | 111 | Additional types 112 | ---------------- 113 | 114 | `o.any()` accepts multiple types that a parameter will match against. 115 | 116 | ```javascript 117 | var method = overload().args(o.any(String, Number)).use(function() { 118 | console.log('passed!'); 119 | }); 120 | 121 | method(); // fails 122 | method([]); // fails 123 | method(''); // passed! 124 | method(0); // passed! 125 | ``` 126 | 127 | The inverse of `o.any` is `o.except`. 128 | 129 | ```javascript 130 | var method = overload().args(o.except(Object)).use(function() { 131 | console.log('passed!'); 132 | }); 133 | 134 | method(); // passed! 135 | method([]); // passed! 136 | method({}); // fails 137 | ``` 138 | 139 | Also available are `o.truthy` and `o.falsy`. 140 | 141 | ```javascript 142 | var method = overload() 143 | .args(o.truthy).use(function() { 144 | console.log('truthy'); 145 | }) 146 | .args(o.falsy).use(function() { 147 | console.log('falsy'); 148 | }); 149 | 150 | method(); // fails 151 | method(0); // falsy 152 | method(1); // truthy 153 | ``` 154 | 155 | Overloading by length 156 | ---------------- 157 | 158 | In addition to overloading by type, argument length can be used. 159 | If a number is not passed, the `function.length` will be used. 160 | 161 | ```javascript 162 | var method = overload() 163 | .len(0).use(function() { 164 | console.log('0 args'); 165 | }) 166 | .len(1).use(function(a) { 167 | console.log('1 arg'); 168 | }) 169 | .len().use(function(a, b, c) { 170 | console.log('3 args'); 171 | }); 172 | 173 | method(); // '0 args' 174 | method({}); // '1 arg' 175 | method(null, [], {}); // '3 args' 176 | ``` 177 | 178 | alias: `count`, `size` 179 | 180 | If `args` and `length` are used in the overload, args will be matched 181 | first, followed by length. 182 | 183 | Fallback 184 | ---------------- 185 | 186 | A fallback function can be defined via the `fallback` method. 187 | 188 | ```javascript 189 | var method = overload().args(String).use(function(a) { 190 | console.log(a); 191 | }) 192 | .fallback(function() { 193 | console.log('handled!'); 194 | }); 195 | method('hello'); // 'hello' 196 | method(); // 'handled' 197 | ``` 198 | 199 | If a fallback is not defined and the exposed method is called 200 | without a matching function, an error will be thrown... 201 | 202 | Expose 203 | ---------------- 204 | 205 | A clean function can be exposed so that overload properties 206 | and methods are hidden from the outside world. 207 | 208 | ```javascript 209 | var method = overload() 210 | .args().use(function() { 211 | return 'hi'; 212 | }) 213 | .expose(); 214 | 215 | // method.args === undefined 216 | // method.use === undefined 217 | // etc... 218 | ``` 219 | 220 | Errors 221 | ---------------- 222 | 223 | The error from unmatched calls can be handled by defining your own `err` method on `overload` or 224 | by passing a function to handle the error. 225 | 226 | ```javascript 227 | overload.err = function() { 228 | console.log('there was an error'); 229 | }; 230 | 231 | overload() 232 | .error(function() { console.trace(); }) 233 | ``` 234 | 235 | Support 236 | ---------------- 237 | 238 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/JosephClay/overload-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 239 | 240 | Node.js, modern browsers and IE8+ 241 | 242 | To run the tests, simply open test/index.html in your favorite browser or run `npm test`. 243 | 244 | #License 245 | 246 | The MIT License (MIT) 247 | 248 | Copyright (c) 2014 Joseph Clay 249 | 250 | Permission is hereby granted, free of charge, to any person obtaining a copy 251 | of this software and associated documentation files (the "Software"), to deal 252 | in the Software without restriction, including without limitation the rights 253 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 254 | copies of the Software, and to permit persons to whom the Software is 255 | furnished to do so, subject to the following conditions: 256 | 257 | The above copyright notice and this permission notice shall be included in 258 | all copies or substantial portions of the Software. 259 | 260 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 261 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 262 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 263 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 264 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 265 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 266 | THE SOFTWARE. 267 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | fs = require('fs'), 3 | moment = require('moment'), 4 | uglify = require('gulp-uglify'), 5 | gzip = require('gulp-gzip'), 6 | rename = require('gulp-rename'), 7 | header = require('gulp-header'), 8 | pkg = require('./package.json'), 9 | UGLIFY_OPTS = { 10 | fromString: true, 11 | mangle: { 12 | sort: true, 13 | toplevel: true, 14 | eval: true 15 | }, 16 | compress: { 17 | screw_ie8: true, 18 | properties: true, 19 | unsafe: true, 20 | sequences: true, 21 | dead_code: true, 22 | conditionals: true, 23 | booleans: true, 24 | unused: true, 25 | if_return: true, 26 | join_vars: true, 27 | drop_console: true, 28 | comparisons: true, 29 | loops: true, 30 | cascade: true, 31 | warnings: true, 32 | negate_iife: true, 33 | pure_getters: true 34 | } 35 | }; 36 | 37 | gulp.task('min', function() { 38 | gulp.src('overload.js') 39 | .pipe(uglify(UGLIFY_OPTS)) 40 | .pipe(rename('overload.min.js')) 41 | .pipe(gulp.dest('./')); 42 | }); 43 | 44 | gulp.task('zip', function() { 45 | gulp.src('overload.min.js') 46 | .pipe(gzip({ append: true })) 47 | .pipe(gulp.dest('./')); 48 | }); 49 | 50 | gulp.task('banner', function() { 51 | var file = fs.readFileSync('./overload.min.js').toString(); 52 | file = file.replace(/^\/\*(.|\n)+\*\//, ''); 53 | fs.writeFileSync('./overload.min.js', file); 54 | 55 | var banner = [ 56 | '/*! ${title} - v${version} - ${date}\n', 57 | ' * ${homepage}\n', 58 | ' * Copyright (c) 2013-${year} ${author}; License: ${license} */\n' 59 | ].join(''); 60 | 61 | gulp.src('overload.min.js') 62 | .pipe(header(banner, { 63 | title: pkg.title || pkg.name, 64 | version: pkg.version, 65 | date: moment().format('YYYY-MM-DD'), 66 | homepage: pkg.homepage, 67 | author: pkg.author.name, 68 | year: moment().format('YYYY'), 69 | license: pkg.license 70 | })) 71 | .pipe(gulp.dest('./')); 72 | 73 | }); 74 | 75 | gulp.task('default', function() { 76 | gulp.start([ 77 | 'min', 78 | 'zip', 79 | 'banner' 80 | ]); 81 | }); -------------------------------------------------------------------------------- /overload.js: -------------------------------------------------------------------------------- 1 | (function(TRUE, FALSE, NULL, undefined) { 2 | 3 | var root = this; 4 | 5 | // Variablizing the strings for consistency 6 | // and to avoid harmful dot-notation look-ups with 7 | // javascript keywords 8 | var sNull = 'Null', 9 | sUndefined = 'Undefined', 10 | sInfinity = 'Infinity', 11 | sDate = 'Date', 12 | sNaN = 'NaN', 13 | sNumber = 'Number', 14 | sString = 'String', 15 | sObject = 'Object', 16 | sArray = 'Array', 17 | sRegExp = 'RegExp', 18 | sBoolean = 'Boolean', 19 | sFunction = 'Function', 20 | sElement = 'Element'; 21 | 22 | // Utilizing the non-standard (but available in modern browsers) Global Object names 23 | // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name 24 | // Provide a polyfill for items without names 25 | (function() { 26 | var globalObjects = [ 27 | sDate, 28 | sNumber, 29 | sString, 30 | sObject, 31 | sArray, 32 | sRegExp, 33 | sBoolean, 34 | sFunction, 35 | sElement 36 | ], 37 | idx = globalObjects.length, 38 | globalObject; 39 | while (idx--) { 40 | globalObject = globalObjects[idx]; 41 | if (root[globalObject] !== undefined) { 42 | if (!root[globalObject].name) { 43 | root[globalObject].name = globalObject; 44 | } 45 | } 46 | } 47 | }()); 48 | 49 | /** 50 | * Possible values 51 | * @type {Object} 52 | */ 53 | var _types = {}; 54 | _types[sNull] = 0; 55 | _types[sUndefined] = 1; 56 | _types[sInfinity] = 2; 57 | _types[sDate] = 3; 58 | _types[sNaN] = 4; 59 | _types[sNumber] = 5; 60 | _types[sString] = 6; 61 | _types[sObject] = 7; 62 | _types[sArray] = 8; 63 | _types[sRegExp] = 9; 64 | _types[sBoolean] = 10; 65 | _types[sFunction] = 11; 66 | _types[sElement] = 12; 67 | 68 | /** 69 | * Cached reference to Object.prototype.toString 70 | * for type checking 71 | * @type {Function} 72 | */ 73 | var _toString = (function(toString) { 74 | return function(obj) { 75 | return toString.call(obj); 76 | }; 77 | }(({}).toString)), 78 | 79 | _noopArr = [], 80 | 81 | /** 82 | * Type checks 83 | */ 84 | _checkMap = (function(map) { 85 | 86 | var types = [ 87 | // Only mapping items that need to be mapped. 88 | // Items not in this list are doing faster 89 | // (non-string) checks 90 | // 91 | // 0 = key, 1 = value 92 | [ sDate, _types[sDate] ], 93 | [ sNumber, _types[sNumber] ], 94 | [ sString, _types[sString] ], 95 | [ sObject, _types[sObject] ], 96 | [ sArray, _types[sArray] ], 97 | [ sRegExp, _types[sRegExp] ], 98 | [ sFunction, _types[sFunction] ] 99 | ], 100 | idx = types.length; 101 | while (idx--) { 102 | map['[object ' + types[idx][0] + ']'] = types[idx][1]; 103 | } 104 | 105 | return map; 106 | 107 | }({})), 108 | 109 | /** 110 | * Mini extend 111 | * @param {Function} base 112 | * @param {Object} obj 113 | * @return {Function} base 114 | */ 115 | extend = function(base, obj) { 116 | var key; 117 | for (key in obj) { 118 | base[key] = obj[key]; 119 | } 120 | return base; 121 | }; 122 | 123 | var callLengths = { 124 | 0: function(fn, args, context) { 125 | if (!context) { return fn(); } 126 | return fn.call(context); 127 | }, 128 | 1: function(fn, args, context) { 129 | return fn.call(context, args[0]); 130 | }, 131 | 2: function(fn, args, context) { 132 | return fn.call(context, args[0], args[1]); 133 | }, 134 | 3: function(fn, args, context) { 135 | return fn.call(context, args[0], args[1], args[2]); 136 | } 137 | }; 138 | var caller = function(fn, args, context) { 139 | var call = callLengths[args.length]; 140 | if (call) { return call(fn, args, context); } 141 | return fn.apply(context, args); 142 | }; 143 | 144 | var _getConfigurationType = function(val) { 145 | if (val === null) { return _types[sNull]; } 146 | if (val === undefined) { return _types[sUndefined]; } 147 | 148 | // we have something, but don't know what 149 | if (!val.name) { 150 | if (val === root[sElement]) { return _types[sElement]; } // Firefox doesn't allow setting the name of Element 151 | if (val !== +val) { return _types[sNaN]; } // NaN check 152 | return _types[sInfinity]; // Infinity check 153 | } 154 | 155 | return _types[val.name]; 156 | }; 157 | 158 | var _getParameterType = function(val) { 159 | if (val === null) { return _types[sNull]; } 160 | if (val === undefined) { return _types[sUndefined]; } 161 | if (val === TRUE || val === FALSE) { return _types[sBoolean]; } 162 | if (val && val.nodeType === 1) { return _types[sElement]; } // Element check from Underscore 163 | 164 | var typeString = _toString(val); 165 | if (_checkMap[typeString] === _types[sNumber]) { 166 | if (val !== +val) { return _types[sNaN]; } // NaN check 167 | if (!isFinite(val)) { return _types[sInfinity]; } // Finite check 168 | return _types[sNumber]; // definitely a number 169 | } 170 | 171 | return _checkMap[typeString]; 172 | }; 173 | 174 | var _convertConfigurationTypes = function(args) { 175 | var parameters = [], 176 | idx = 0, length = args.length, 177 | configItem; 178 | for (; idx < length; idx++) { 179 | configItem = args[idx]; 180 | parameters.push( 181 | (configItem instanceof Custom) ? configItem : _getConfigurationType(configItem) 182 | ); 183 | } 184 | return parameters; 185 | }; 186 | 187 | var _convertConfigurationMap = function(map) { 188 | var parameters = {}, 189 | key, configItem; 190 | for (key in map) { 191 | configItem = map[key]; 192 | parameters[key] = (configItem instanceof Custom) ? configItem : _getConfigurationType(configItem); 193 | } 194 | return parameters; 195 | }; 196 | 197 | var _convertParametersTypes = function(args) { 198 | var parameters = [], 199 | idx = 0, length = args.length; 200 | for (; idx < length; idx++) { 201 | parameters.push(_getParameterType(args[idx])); 202 | } 203 | return parameters; 204 | }; 205 | 206 | var _doesMapMatchArgsTypes = function(map, argTypes, args) { 207 | var mapLength = map.length, 208 | argLength = argTypes.length; 209 | 210 | if (mapLength === 0 && argLength === 0) { return TRUE; } 211 | if (mapLength !== argLength) { return FALSE; } 212 | 213 | var idx = 0, 214 | mapItem; 215 | for (; idx < argLength; idx++) { 216 | mapItem = map[idx]; 217 | 218 | if (mapItem instanceof Custom) { 219 | if (mapItem.check(args[idx])) { 220 | continue; 221 | } 222 | return FALSE; 223 | } 224 | 225 | if (argTypes[idx] !== mapItem) { 226 | return FALSE; 227 | } 228 | } 229 | 230 | return TRUE; 231 | }; 232 | 233 | var _getArgumentMatch = function(mappings, args) { 234 | if (!mappings) { return; } 235 | 236 | var argTypes = _convertParametersTypes(args), 237 | idx = 0, length = mappings.length; 238 | for (; idx < length; idx++) { 239 | if (_doesMapMatchArgsTypes(mappings[idx].params, argTypes, args)) { 240 | return mappings[idx]; 241 | } 242 | } 243 | }; 244 | 245 | var _getLengthMatch = function(mappings, args) { 246 | if (!mappings) { return; } 247 | 248 | var argLength = args.length, 249 | idx = 0, length = mappings.length; 250 | for (; idx < length; idx++) { 251 | if (mappings[idx].length === argLength) { 252 | return mappings[idx]; 253 | } 254 | } 255 | }; 256 | 257 | var _matchAny = function(args, val) { 258 | var type = _getParameterType(val), 259 | idx = args.length, 260 | mapItem; 261 | 262 | while (idx--) { 263 | mapItem = args[idx]; 264 | 265 | if (mapItem instanceof Custom) { 266 | if (mapItem.check(val)) { 267 | return TRUE; 268 | } 269 | continue; 270 | } 271 | 272 | if (args[idx] === type) { 273 | return TRUE; 274 | } 275 | } 276 | 277 | return FALSE; 278 | }; 279 | 280 | var _matchMap = function(config, map) { 281 | var key, configItem, mapItem; 282 | for (key in config) { 283 | configItem = config[key]; 284 | mapItem = map[key]; 285 | 286 | if (configItem instanceof Custom) { 287 | if (!configItem.check(mapItem)) { 288 | return FALSE; 289 | } 290 | continue; 291 | } 292 | 293 | if (configItem !== _getParameterType(mapItem)) { 294 | return FALSE; 295 | } 296 | } 297 | 298 | return TRUE; 299 | }; 300 | 301 | /** 302 | * Custom type that validates a value 303 | * @constructor 304 | * @param {Function} check 305 | */ 306 | var Custom = function(check) { 307 | this.check = check; 308 | }; 309 | 310 | var o = { 311 | wild: new Custom(function() { 312 | return TRUE; 313 | }), 314 | truthy: new Custom(function(val) { 315 | return !!val === TRUE; 316 | }), 317 | falsy: new Custom(function(val) { 318 | return !!val === FALSE; 319 | }), 320 | any: function() { 321 | var args = _convertConfigurationTypes(arguments); 322 | return new Custom(function(val) { 323 | return _matchAny(args, val); 324 | }); 325 | }, 326 | except: function() { 327 | var args = _convertConfigurationTypes(arguments); 328 | return new Custom(function(val) { 329 | return !_matchAny(args, val); 330 | }); 331 | }, 332 | map: function(map) { 333 | var mapConfig = _convertConfigurationMap(map); 334 | return new Custom(function(map) { 335 | return _matchMap(mapConfig, map); 336 | }); 337 | } 338 | }; 339 | 340 | var fn = { 341 | /** 342 | * Methods mapped to argument types 343 | * Lazily instanciated 344 | * @type {Array} argument mapping 345 | */ 346 | // this._m; 347 | 348 | /** 349 | * Methods mapped to argument lengths 350 | * Lazily instanciated 351 | * @type {Array} length mapping 352 | */ 353 | // this._l; 354 | 355 | /** 356 | * A fallback function if none 357 | * of the criteria match on a call 358 | * @type {Function} 359 | */ 360 | // this._f; 361 | 362 | map: function(map) { 363 | var self = this; 364 | 365 | return { 366 | use: function(method) { 367 | var argMappings = self._m || (self._m = []); 368 | argMappings.push({ 369 | params: [o.map(map)], 370 | method: method 371 | }); 372 | return self; 373 | } 374 | }; 375 | }, 376 | 377 | args: function() { 378 | var self = this, 379 | args = arguments; 380 | 381 | return { 382 | use: function(method) { 383 | var argMappings = self._m || (self._m = []); 384 | argMappings.push({ 385 | params: _convertConfigurationTypes(args), 386 | method: method 387 | }); 388 | return self; 389 | } 390 | }; 391 | }, 392 | 393 | len: function(num) { 394 | var self = this; 395 | return { 396 | use: function(method) { 397 | var lengthMappings = self._l || (self._l = []); 398 | lengthMappings.push({ 399 | length: (num === undefined) ? method.length : num, 400 | method: method 401 | }); 402 | return self; 403 | } 404 | }; 405 | }, 406 | 407 | error: function(method) { 408 | this._err = method; 409 | return this; 410 | }, 411 | 412 | fallback: function(method) { 413 | this._f = method; 414 | return this; 415 | }, 416 | 417 | call: function() { 418 | // prevent function deoptimation 419 | var args = arguments, a = []; 420 | for (var idx = 1, length = args.length; idx < length; idx++) { 421 | a[idx] = args[idx]; 422 | } 423 | return this._call(args[0], a); 424 | }, 425 | 426 | apply: function(context, args) { 427 | var a = args; 428 | if (args && args.callee) { 429 | // passed an arguments object, 430 | // not an array. 431 | // prevent function deoptimation 432 | a = []; 433 | for (var idx = 0, length = args.length; idx < length; idx++) { 434 | a[idx] = args[idx]; 435 | } 436 | } 437 | return this._call(context, a); 438 | }, 439 | 440 | bind: function(context) { 441 | var self = this; 442 | return function() { 443 | // prevent function deoptimation 444 | var args = arguments, a = []; 445 | for (var idx = 0, length = args.length; idx < length; idx++) { 446 | a[idx] = args[idx]; 447 | } 448 | return self._call(context, a); 449 | }; 450 | }, 451 | 452 | expose: function() { 453 | var self = this; 454 | return function() { 455 | // prevent function deoptimation 456 | var args = arguments, a = []; 457 | for (var idx = 0, length = args.length; idx < length; idx++) { 458 | a[idx] = args[idx]; 459 | } 460 | 461 | return self._call(this, a); 462 | }; 463 | }, 464 | 465 | _call: function(context, args) { 466 | if (context === root) { context = null; } 467 | 468 | args = args || _noopArr; 469 | 470 | // Any argument match, of course, already matches 471 | // the length match, so this should be done first 472 | var argMatch = _getArgumentMatch(this._m, args); 473 | if (argMatch) { 474 | return caller(argMatch.method, args, context); 475 | } 476 | 477 | // Check for a length match 478 | var lengthMatch = _getLengthMatch(this._l, args); 479 | if (lengthMatch) { 480 | return caller(lengthMatch.method, args, context); 481 | } 482 | 483 | // Check for a fallback 484 | if (this._f) { 485 | return caller(this._f, args, context); 486 | } 487 | 488 | // Error 489 | return this._err ? this._err(args) : api.err; 490 | } 491 | }; 492 | 493 | fn.fail = fn.err = fn.error; 494 | fn.count = fn.size = fn.len; 495 | 496 | var api = function() { 497 | var overload = function overload() { 498 | return overload._call(overload, arguments); 499 | }; 500 | return extend(overload, fn); 501 | }; 502 | api.o = o; 503 | api.fn = fn; 504 | api.err = function() { 505 | throw 'overload - exception: No methods matched'; 506 | }; 507 | api.define = api.defineType = function(name, check) { 508 | var custom = new Custom(check); 509 | return (o[name] = custom); 510 | }; 511 | api.defineTypes = function(obj) { 512 | var key; 513 | for (key in obj) { 514 | api.define(key, obj[key]); 515 | } 516 | return api; 517 | }; 518 | 519 | if (typeof define === 'function') { // RequireJS 520 | define(function() { return api; }); 521 | } else if (typeof module !== 'undefined' && module.exports) { // CommonJS 522 | module.exports = api; 523 | } else { 524 | root.overload = api; 525 | root.o = o; 526 | } 527 | 528 | }(true, false, null)); 529 | -------------------------------------------------------------------------------- /overload.min.js: -------------------------------------------------------------------------------- 1 | /*! overload-js - v1.0.0 - 2015-04-01 2 | * https://github.com/JosephClay/overload-js 3 | * Copyright (c) 2013-2015 Joe Clay; License: MIT */ 4 | !function(u,s){var g="Null",m="Undefined",v="Infinity",f="Date",_="NaN",e="Number",h="String",o="Object",a="Array",p="RegExp",d="Boolean",c="Function",i="Element";!function(){for(var n,t=[f,e,h,o,a,p,d,c,i],r=t.length;r--;)n=t[r],u[n].name||(u[n].name=n)}();var n={};n[g]=0,n[m]=1,n[v]=2,n[f]=3,n[_]=4,n[e]=5,n[h]=6,n[o]=7,n[a]=8,n[p]=9,n[d]=10,n[c]=11,n[i]=12;var S=function(n){return function(t){return n.call(t)}}({}.toString),E=[],k=function(u){for(var t=[{k:f,v:n[f]},{k:e,v:n[e]},{k:h,v:n[h]},{k:o,v:n[o]},{k:a,v:n[a]},{k:p,v:n[p]},{k:c,v:n[c]}],r=t.length;r--;)u["[object "+t[r].k+"]"]=t[r].v;return u}({}),l=function(n){return function(t){return n.call(t)}}([].slice);"bind"in Function.prototype||(Function.prototype.bind=function(n){var t=this;if(1>=arguments.length)return function(){return t.apply(n,arguments)};var r=l(arguments);return function(){return t.apply(n,0===arguments.length?r:r.concat(l(arguments)))}});var N=function(t){return null===t?n[g]:t===s?n[m]:t.name?n[t.name]:t===u[i]?n[i]:t!==+t?n[_]:n[v]},w=function(t){if(null===t)return n[g];if(t===s)return n[m];if(t===!0||t===!1)return n[d];if(t&&1===t.nodeType)return n[i];var r=S(t);return k[r]===n[e]?t!==+t?n[_]:isFinite(t)?n[e]:n[v]:k[r]},y=function(e){for(var n,u=[],t=0,i=e.length;i>t;t++)n=e[t],u.push(n instanceof r?n:N(n));return u},j=function(t){for(var r=[],n=0,e=t.length;e>n;n++)r.push(w(t[n]));return r},O=function(u,i,a){var o=u.length,e=i.length;if(0===o&&0===e)return!0;if(o!==e)return!1;for(var t,n=0;e>n;n++){if(t=u[n],t instanceof r){if(t.check(a[n]))continue;return!1}if(i[n]!==t)return!1}return!0},M=function(n,r){if(!n)return null;for(var e=j(r),t=0,u=n.length;u>t;t++)if(O(n[t].params,e,r))return n[t];return null},F=function(n,r){if(!n)return null;for(var e=r.length,t=0,u=n.length;u>t;t++)if(n[t].length===e)return n[t];return null},b=function(n,u){for(var t,i=w(u),e=n.length;e--;)if(t=n[e],t instanceof r){if(t.check(u))return!0}else if(n[e]===i)return!0;return!1},r=function(n){this.check=n},x={wild:new r(function(){return!0}),truthy:new r(function(n){return!!n==!0}),falsy:new r(function(n){return!!n==!1}),any:function(){var n=y(arguments);return new r(function(t){return b(n,t)})},except:function(){var n=y(arguments);return new r(function(t){return!b(n,t)})}},t=function(){return this instanceof t?s:new t};t.o=x,t.defineType=function(n,t){var e=new r(t);return x[n]=e},t.prototype={constructor:t,args:function(){var n=this,t=arguments;return{use:function(r){var e=n._argMaps||(n._argMaps=[]);return e.push({params:y(t),method:r}),n}}},length:function(t){var n=this;return{use:function(r){var e=n._lenMaps||(n._lenMaps=[]);return e.push({length:t===s?r.length:t,method:r}),n}}},err:function(){throw"Overload - exception: No methods matched"},fallback:function(n){return this._f=n,this},call:function(){var n=l(arguments);return this._call(n.shift(),n)},apply:function(t,n){return n=n&&n.callee?l(n):n,this._call(t,n)},bind:function(n){var t=this;return function(){return t._call(n,arguments)}.bind(n)},expose:function(){var n=this;return function(){return n._call(this,arguments)}},_call:function(t,n){n=n||E;var r=M(this._argMaps,n);if(r)return r.method.apply(t,n);var e=F(this._lenMaps,n);return e?e.method.apply(t,n):this._f?this._f.apply(t,n):this.err(n)}},"function"==typeof define?define(function(){return t}):"undefined"!=typeof module&&module.exports?module.exports=t:(u.overload=t,u.o=t.o)}(window);rn t}):"undefined"!=typeof module&&module.exports?module.exports=t:(o.overload=t,o.o=d)}(!0,!1,null);null);le&&module.exports?module.exports=r:(o.overload=r,o.o=s)}(!0,!1,null);h,r.fn=i,r.err=function(){throw"overload - exception: No methods matched"},r.define=r.defineType=function(n,r){var e=new t(r);return h[n]=e},r.defineTypes=function(t){var n;for(n in t)r.define(n,t[n]);return r},"function"==typeof define?define(function(){return r}):"undefined"!=typeof module&&module.exports?module.exports=r:(f.overload=r,f.o=h)}(!0,!1,null);); -------------------------------------------------------------------------------- /overload.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JosephClay/overload-js/0bf77fd583fd6132ddaebdd5d7b6987b15adef21/overload.min.js.gz -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overload-js", 3 | "title": "overload-js", 4 | "main": "overload.js", 5 | "description": "Method overloading in JavaScript", 6 | "version": "1.0.0", 7 | "homepage": "https://github.com/JosephClay/overload-js", 8 | "license": "MIT", 9 | "scripts": { 10 | "test": "npm install && nodeunit ./test/node.js" 11 | }, 12 | "author": { 13 | "name": "Joe Clay", 14 | "email": "j053phclay@gmail.com" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/JosephClay/overload-js.git" 19 | }, 20 | "devDependencies": { 21 | "gulp": "^3.8.9", 22 | "gulp-gzip": "0.0.8", 23 | "gulp-header": "^1.2.2", 24 | "gulp-rename": "^1.2.0", 25 | "gulp-uglify": "^1.0.1", 26 | "moment": "^2.8.3", 27 | "nodeunit": "^0.9.0", 28 | "underscore": "^1.7.0" 29 | }, 30 | "dependencies": {} 31 | } 32 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Overload.js 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/node.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'), 2 | overload = require('../overload'), 3 | o = overload.o; 4 | 5 | module.exports = { 6 | basics: function(test) { 7 | test.ok(overload, 'overload exists'); 8 | test.ok(overload(), 'overload object created'); 9 | test.ok(typeof overload() === 'function', 'overload object is a function'); 10 | 11 | test.ok(o, 'o exists'); 12 | test.ok(o.any, 'o.any exists'); 13 | test.ok(o.truthy, 'o.truthy exists'); 14 | test.ok(o.falsy, 'o.falsy exists'); 15 | 16 | test.done(); 17 | }, 18 | 19 | method_registration: function(test) { 20 | var a = function() { return false; }, 21 | b = function() { return true; }; 22 | 23 | var o = overload() 24 | .args().use(a); 25 | 26 | test.ok(o, 'Exposed as a is a function'); 27 | test.strictEqual(o(), false, 'Method registered and called'); 28 | test.strictEqual(o(), false, 'Method returns return value'); 29 | test.strictEqual(o(), false, 'overload can be used without arguments'); 30 | 31 | test.done(); 32 | }, 33 | 34 | overload_paths: function(test) { 35 | var method = overload() 36 | .args(null).use(function() { return null; }) 37 | .args(undefined).use(function() { return undefined; }) 38 | .args(Infinity).use(function() { return Infinity; }) 39 | .args(Date).use(function() { return Date; }) 40 | .args(NaN).use(function() { return NaN; }) 41 | .args(Number).use(function() { return Number; }) 42 | .args(String).use(function() { return String; }) 43 | .args(Object).use(function() { return Object; }) 44 | .args(Array).use(function() { return Array; }) 45 | .args(RegExp).use(function() { return RegExp; }) 46 | .args(Boolean).use(function() { return Boolean; }) 47 | .args(Function).use(function() { return Function; }); 48 | 49 | var types = [ 50 | { 51 | type: null, 52 | name: 'null', 53 | param: null 54 | }, 55 | { 56 | type: undefined, 57 | name: 'undefined', 58 | param: undefined 59 | }, 60 | { 61 | type: Infinity, 62 | name: 'Infinity', 63 | param: Infinity 64 | }, 65 | { 66 | type: Date, 67 | name: 'Date', 68 | param: new Date() 69 | }, 70 | { 71 | type: Number, 72 | name: 'Number', 73 | param: 0 74 | }, 75 | { 76 | type: String, 77 | name: 'String', 78 | param: '' 79 | }, 80 | { 81 | type: Object, 82 | name: 'Object', 83 | param: {} 84 | }, 85 | { 86 | type: Array, 87 | name: 'Array', 88 | param: [] 89 | }, 90 | { 91 | type: RegExp, 92 | name: 'RegExp', 93 | param: /-/gi 94 | }, 95 | { 96 | type: Boolean, 97 | name: 'Boolean', 98 | param: false 99 | }, 100 | { 101 | type: Function, 102 | name: 'Function', 103 | param: function() {} 104 | } 105 | ]; 106 | 107 | var idx = types.length; 108 | while (idx--) { 109 | var type = types[idx]; 110 | test.strictEqual(method(type.param), type.type, type.name + ' passed overload test'); 111 | } 112 | 113 | // NaN is special because NaN !== NaN 114 | test.strictEqual(_.isNaN(method(NaN)), _.isNaN(NaN), 'NaN passed overload test'); 115 | 116 | test.done(); 117 | }, 118 | 119 | this_context: function(test) { 120 | var method = overload() 121 | .args() 122 | .use(function() { return this; }); 123 | 124 | test.equal(method.call('one'), 'one', 'Exposed method can be called with context'); 125 | test.equal(method.apply('two'), 'two', 'Exposed method can be applied with context'); 126 | test.equal(method.bind('three')(), 'three', 'Exposed method can be bound with context'); 127 | 128 | test.done(); 129 | }, 130 | 131 | wild: function(test) { 132 | var method = overload() 133 | .error(function() { return 'error'; }) 134 | .args(o.wild).use(function() { return true; }); 135 | 136 | test.strictEqual(method(1), true, 'wild works with #1'); 137 | test.strictEqual(method(0), true, 'wild works with #0'); 138 | test.strictEqual(method(true), true, 'wild works with true'); 139 | test.strictEqual(method(false), true, 'wild works with false'); 140 | test.strictEqual(method('1'), true, 'wild works with "1"'); 141 | test.strictEqual(method({}), true, 'wild works with Object'); 142 | test.strictEqual(method([]), true, 'wild works with Array'); 143 | test.strictEqual(method(undefined), true, 'wild works with undefined'); 144 | test.strictEqual(method(null), true, 'wild works with null'); 145 | test.strictEqual(method(), 'error', 'wild throws error with no param'); 146 | 147 | test.done(); 148 | }, 149 | 150 | truthy: function(test) { 151 | var method = overload() 152 | .error(function() { return 'error'; }) 153 | .args(o.truthy).use(function() { return true; }); 154 | 155 | test.strictEqual(method(1), true, 'truthy works with #1'); 156 | test.strictEqual(method(true), true, 'truthy works with true'); 157 | test.strictEqual(method('1'), true, 'truthy works with "1"'); 158 | test.strictEqual(method({}), true, 'truthy works with Object'); 159 | test.strictEqual(method([]), true, 'truthy works with Array'); 160 | test.strictEqual(method(0), 'error', 'truthy throws error with falsy value'); 161 | 162 | test.done(); 163 | }, 164 | 165 | falsy: function(test) { 166 | var method = overload() 167 | .error(function() { return 'error'; }) 168 | .args(o.falsy).use(function() { return false; }); 169 | 170 | test.strictEqual(method(0), false, 'falsy works with #0'); 171 | test.strictEqual(method(false), false, 'falsy works with false'); 172 | test.strictEqual(method(''), false, 'falsy works with ""'); 173 | test.strictEqual(method(null), false, 'falsy works with null'); 174 | test.strictEqual(method(undefined), false, 'falsy works with undefined'); 175 | test.strictEqual(method(1), 'error', 'falsy throws error with truthy value'); 176 | 177 | test.done(); 178 | }, 179 | 180 | length: function(test) { 181 | var a = function() { return 1; }, 182 | b = function() { return 2; }, 183 | c = function() { return 3; }; 184 | 185 | var method = overload() 186 | .len(0).use(a) 187 | .len(1).use(b) 188 | .len(2).use(c); 189 | 190 | test.strictEqual(method(), 1, 'No params called first function'); 191 | test.strictEqual(method(1), 2, 'One param called second function'); 192 | test.strictEqual(method(1, 2), 3, 'Two params called third function'); 193 | 194 | var method2 = overload() 195 | .len().use(function(a, b, c, d) { return 4; }); 196 | 197 | test.strictEqual(method2(1, 2, 3, 4), 4, 'No length gets length from function'); 198 | 199 | test.done(); 200 | }, 201 | 202 | any: function(test) { 203 | var method = overload() 204 | .error(function() { return 'error'; }) 205 | .args(o.any(String, Boolean, Date)).use(function() { return 0; }) 206 | .args(o.any(Array, Object, Function)).use(function() { return 1; }); 207 | 208 | test.strictEqual(method(''), 0, 'Any first test passed'); 209 | test.strictEqual(method(true), 0, 'Any first test passed'); 210 | test.strictEqual(method(new Date()), 0, 'Any first test passed'); 211 | 212 | test.strictEqual(method([]), 1, 'Any second test passed'); 213 | test.strictEqual(method({}), 1, 'Any second test passed'); 214 | test.strictEqual(method(function() {}), 1, 'Any second test passed'); 215 | 216 | test.done(); 217 | }, 218 | 219 | except: function(test) { 220 | var method = overload() 221 | .error(function() { return 'error'; }) 222 | .args(o.except(String, Boolean, Date)).use(function() { return 0; }) 223 | .args(o.except(Array, Object, Function)).use(function() { return 1; }); 224 | 225 | test.strictEqual(method(''), 1, 'Expect first test passed'); 226 | test.strictEqual(method(true), 1, 'Expect first test passed'); 227 | test.strictEqual(method(new Date()), 1, 'Expect first test passed'); 228 | 229 | test.strictEqual(method([]), 0, 'Expect second test passed'); 230 | test.strictEqual(method({}), 0, 'Expect second test passed'); 231 | test.strictEqual(method(function() {}), 0, 'Expect second test passed'); 232 | 233 | test.done(); 234 | }, 235 | 236 | map: function(test) { 237 | var a = function() { return true; }; 238 | 239 | var method = overload() 240 | .error(function() { return 'error'; }) 241 | .args(o.map({ foo: String, bar: Boolean, baz: Date })).use(a); 242 | 243 | test.strictEqual(method(''), 'error', 'Expect error'); 244 | test.strictEqual(method(true), 'error', 'Expect error'); 245 | test.strictEqual(method(new Date()), 'error', 'Expect error'); 246 | 247 | test.strictEqual(method({ foo: '', bar: false }), 'error', 'Expect error - missing bar'); 248 | test.strictEqual(method({ foo: '', baz: new Date() }), 'error', 'Expect error - missing baz'); 249 | test.strictEqual(method({ bar: false, baz: new Date() }), 'error', 'Expect error - missing foo'); 250 | 251 | test.strictEqual(method({ foo: '', bar: false, baz: new Date() }), true, 'Expect pass - fulfilled requirements'); 252 | test.strictEqual(method({ foo: '', bar: false, baz: new Date(), foo2: '' }), true, 'Expect pass - extra data ignored'); 253 | 254 | // reset for undefined key test 255 | method = overload() 256 | .error(function() { return 'error'; }) 257 | .args(o.map({ foo: undefined, bar: Boolean })).use(a); 258 | 259 | test.strictEqual(method({ bar: false }), true, 'Expect pass - missing key that should be undefined is ignored'); 260 | 261 | // reset for convenience method 262 | method = overload() 263 | .error(function() { return 'error'; }) 264 | .map({ foo: String, bar: Boolean, baz: Date }).use(a); 265 | 266 | test.strictEqual(method(''), 'error', 'Expect error'); 267 | test.strictEqual(method(true), 'error', 'Expect error'); 268 | test.strictEqual(method(new Date()), 'error', 'Expect error'); 269 | 270 | test.strictEqual(method({ foo: '', bar: false }), 'error', 'Expect error - missing bar'); 271 | test.strictEqual(method({ foo: '', baz: new Date() }), 'error', 'Expect error - missing baz'); 272 | test.strictEqual(method({ bar: false, baz: new Date() }), 'error', 'Expect error - missing foo'); 273 | 274 | test.strictEqual(method({ foo: '', bar: false, baz: new Date() }), true, 'Expect pass - fulfilled requirements'); 275 | test.strictEqual(method({ foo: '', bar: false, baz: new Date(), foo2: '' }), true, 'Expect pass - extra data ignored'); 276 | 277 | test.done(); 278 | }, 279 | 280 | fallback: function(test) { 281 | var method = overload() 282 | .args(String).use(function() {}) 283 | .fallback(function() { return 0; }); 284 | 285 | test.strictEqual(method(), 0, 'Fallback function called'); 286 | 287 | test.done(); 288 | }, 289 | 290 | error: function(test) { 291 | var method = overload() 292 | .args(String).use(function() { return 0; }) 293 | .error(function() { return 'error'; }); 294 | 295 | test.strictEqual(!!method, true, '.error successfully chains'); 296 | test.strictEqual(method(''), 0, '.error chained succeeds'); 297 | test.strictEqual(method(), 'error', '.error chained fails'); 298 | 299 | test.done(); 300 | }, 301 | 302 | expose: function(test) { 303 | var method = overload() 304 | .args(String).use(function() { return 0; }) 305 | .error(function() { return 'error'; }) 306 | .expose(); 307 | 308 | test.strictEqual(method(''), 0, 'Exposed function suceeds'); 309 | test.strictEqual(method(), 'error', 'Exposed function fails'); 310 | test.strictEqual(method.args, undefined, 'Exposed function is clean'); 311 | 312 | test.done(); 313 | }, 314 | 315 | passed_parameters: function(test) { 316 | var method = overload() 317 | .args(o.any(String, Number, Boolean)).use(function(param) { return param; }) 318 | .fallback(function() { return 'fallback'; }); 319 | 320 | test.equal(method('one'), 'one', 'String passed and returned'); 321 | test.equal(method(2), 2, 'Number passed and returned'); 322 | test.equal(method(true), true, 'Boolean passed and returned'); 323 | test.equal(method(null), 'fallback', 'No items matched and defered to the fallback'); 324 | 325 | test.done(); 326 | }, 327 | 328 | custom: function(test) { 329 | var Thingy = function() {}; 330 | overload.defineType('thingy', function(val) { 331 | return (val instanceof Thingy); 332 | }); 333 | 334 | test.ok(o.thingy, 'Custom type added'); 335 | 336 | var a = function() { return 0; }; 337 | 338 | var method1 = overload().args(o.thingy).use(a); 339 | test.strictEqual(method1(new Thingy()), 0, 'Custom function works as a definition'); 340 | 341 | var method2 = overload().args(o.any(Boolean, o.thingy)).use(a); 342 | test.strictEqual(method2(new Thingy()), 0, 'Custom function work inside any() custom definition'); 343 | 344 | test.done(); 345 | } 346 | }; -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | test('basics', function() { 2 | ok(overload, 'overload exists'); 3 | ok(overload(), 'overload object created'); 4 | ok(typeof overload() === 'function', 'overload object is a function'); 5 | 6 | ok(o, 'o exists'); 7 | ok(o.any, 'o.any exists'); 8 | ok(o.truthy, 'o.truthy exists'); 9 | ok(o.falsy, 'o.falsy exists'); 10 | }); 11 | 12 | test('method registration', function() { 13 | var a = function() { return false; }, 14 | b = function() { return true; }; 15 | 16 | var o = overload() 17 | .args().use(a) 18 | 19 | ok(_.isFunction(o), 'Exposed as a is a function'); 20 | strictEqual(o(), false, 'Method registered and called'); 21 | strictEqual(o(), false, 'Method returns return value'); 22 | strictEqual(o(), false, 'overload can be used without arguments'); 23 | }); 24 | 25 | test('overload paths', function() { 26 | var method = overload() 27 | .args(null).use(function() { return null; }) 28 | .args(undefined).use(function() { return undefined; }) 29 | .args(Infinity).use(function() { return Infinity; }) 30 | .args(Date).use(function() { return Date; }) 31 | .args(NaN).use(function() { return NaN; }) 32 | .args(Number).use(function() { return Number; }) 33 | .args(String).use(function() { return String; }) 34 | .args(Object).use(function() { return Object; }) 35 | .args(Array).use(function() { return Array; }) 36 | .args(RegExp).use(function() { return RegExp; }) 37 | .args(Boolean).use(function() { return Boolean; }) 38 | .args(Function).use(function() { return Function; }) 39 | .args(Element).use(function() { return Element; }); 40 | 41 | var types = [ 42 | { 43 | type: null, 44 | name: 'null', 45 | param: null 46 | }, 47 | { 48 | type: undefined, 49 | name: 'undefined', 50 | param: undefined 51 | }, 52 | { 53 | type: Infinity, 54 | name: 'Infinity', 55 | param: Infinity 56 | }, 57 | { 58 | type: Date, 59 | name: 'Date', 60 | param: new Date() 61 | }, 62 | { 63 | type: Number, 64 | name: 'Number', 65 | param: 0 66 | }, 67 | { 68 | type: String, 69 | name: 'String', 70 | param: '' 71 | }, 72 | { 73 | type: Object, 74 | name: 'Object', 75 | param: {} 76 | }, 77 | { 78 | type: Array, 79 | name: 'Array', 80 | param: [] 81 | }, 82 | { 83 | type: RegExp, 84 | name: 'RegExp', 85 | param: /-/gi 86 | }, 87 | { 88 | type: Boolean, 89 | name: 'Boolean', 90 | param: false 91 | }, 92 | { 93 | type: Function, 94 | name: 'Function', 95 | param: function() {} 96 | }, 97 | { 98 | type: Element, 99 | name: 'Element', 100 | param: document.getElementsByTagName('body')[0] 101 | } 102 | ]; 103 | 104 | var idx = types.length; 105 | while (idx--) { 106 | var type = types[idx]; 107 | strictEqual(method(type.param), type.type, type.name + ' passed overload test'); 108 | } 109 | 110 | // NaN is special because NaN !== NaN 111 | strictEqual(_.isNaN(method(NaN)), _.isNaN(NaN), 'NaN passed overload test'); 112 | }); 113 | 114 | test('"this" context', function() { 115 | var method = overload() 116 | .args() 117 | .use(function() { return this; }); 118 | 119 | equal(method.call('one'), 'one', 'Exposed method can be called with context'); 120 | equal(method.apply('two'), 'two', 'Exposed method can be applied with context'); 121 | equal(method.bind('three')(), 'three', 'Exposed method can be bound with context'); 122 | }); 123 | 124 | test('wild', function() { 125 | var method = overload() 126 | .error(function() { return 'error'; }) 127 | .args(o.wild).use(function() { return true; }); 128 | 129 | strictEqual(method(1), true, 'wild works with #1'); 130 | strictEqual(method(0), true, 'wild works with #0'); 131 | strictEqual(method(true), true, 'wild works with true'); 132 | strictEqual(method(false), true, 'wild works with false'); 133 | strictEqual(method('1'), true, 'wild works with "1"'); 134 | strictEqual(method({}), true, 'wild works with Object'); 135 | strictEqual(method([]), true, 'wild works with Array'); 136 | strictEqual(method(undefined), true, 'wild works with undefined'); 137 | strictEqual(method(null), true, 'wild works with null'); 138 | strictEqual(method(), 'error', 'wild throws error with no param'); 139 | }); 140 | 141 | test('truthy', function() { 142 | var method = overload() 143 | .error(function() { return 'error'; }) 144 | .args(o.truthy).use(function() { return true; }); 145 | 146 | strictEqual(method(1), true, 'truthy works with #1'); 147 | strictEqual(method(true), true, 'truthy works with true'); 148 | strictEqual(method('1'), true, 'truthy works with "1"'); 149 | strictEqual(method({}), true, 'truthy works with Object'); 150 | strictEqual(method([]), true, 'truthy works with Array'); 151 | strictEqual(method(0), 'error', 'truthy throws error with falsy value'); 152 | }); 153 | 154 | test('falsy', function() { 155 | var method = overload() 156 | .error(function() { return 'error'; }) 157 | .args(o.falsy).use(function() { return false; }); 158 | 159 | strictEqual(method(0), false, 'falsy works with #0'); 160 | strictEqual(method(false), false, 'falsy works with false'); 161 | strictEqual(method(''), false, 'falsy works with ""'); 162 | strictEqual(method(null), false, 'falsy works with null'); 163 | strictEqual(method(undefined), false, 'falsy works with undefined'); 164 | strictEqual(method(1), 'error', 'falsy throws error with truthy value'); 165 | }); 166 | 167 | test('length', function() { 168 | var a = function() { return 1; }, 169 | b = function() { return 2; }, 170 | c = function() { return 3; }; 171 | 172 | var method = overload() 173 | .len(0).use(a) 174 | .len(1).use(b) 175 | .len(2).use(c); 176 | 177 | strictEqual(method(), 1, 'No params called first function'); 178 | strictEqual(method(1), 2, 'One param called second function'); 179 | strictEqual(method(1, 2), 3, 'Two params called third function'); 180 | 181 | var method2 = overload() 182 | .len().use(function(a, b, c, d) { return 4; }); 183 | 184 | strictEqual(method2(1, 2, 3, 4), 4, 'No length gets length from function'); 185 | }); 186 | 187 | test('any', function() { 188 | var method = overload() 189 | .error(function() { return 'error'; }) 190 | .args(o.any(String, Boolean, Date)).use(function() { return 0; }) 191 | .args(o.any(Array, Object, Function)).use(function() { return 1; }); 192 | 193 | strictEqual(method(''), 0, 'Any first test passed'); 194 | strictEqual(method(true), 0, 'Any first test passed'); 195 | strictEqual(method(new Date()), 0, 'Any first test passed'); 196 | 197 | strictEqual(method([]), 1, 'Any second test passed'); 198 | strictEqual(method({}), 1, 'Any second test passed'); 199 | strictEqual(method(function() {}), 1, 'Any second test passed'); 200 | }); 201 | 202 | test('except', function() { 203 | var method = overload() 204 | .error(function() { return 'error'; }) 205 | .args(o.except(String, Boolean, Date)).use(function() { return 0; }) 206 | .args(o.except(Array, Object, Function)).use(function() { return 1; }); 207 | 208 | strictEqual(method(''), 1, 'Expect first test passed'); 209 | strictEqual(method(true), 1, 'Expect first test passed'); 210 | strictEqual(method(new Date()), 1, 'Expect first test passed'); 211 | 212 | strictEqual(method([]), 0, 'Expect second test passed'); 213 | strictEqual(method({}), 0, 'Expect second test passed'); 214 | strictEqual(method(function() {}), 0, 'Expect second test passed'); 215 | }); 216 | 217 | test('map', function() { 218 | var a = function() { return true; }; 219 | 220 | var method = overload() 221 | .error(function() { return 'error'; }) 222 | .args(o.map({ foo: String, bar: Boolean, baz: Date })).use(a); 223 | 224 | strictEqual(method(''), 'error', 'Expect error'); 225 | strictEqual(method(true), 'error', 'Expect error'); 226 | strictEqual(method(new Date()), 'error', 'Expect error'); 227 | 228 | strictEqual(method({ foo: '', bar: false }), 'error', 'Expect error - missing bar'); 229 | strictEqual(method({ foo: '', baz: new Date() }), 'error', 'Expect error - missing baz'); 230 | strictEqual(method({ bar: false, baz: new Date() }), 'error', 'Expect error - missing foo'); 231 | 232 | strictEqual(method({ foo: '', bar: false, baz: new Date() }), true, 'Expect pass - fulfilled requirements'); 233 | strictEqual(method({ foo: '', bar: false, baz: new Date(), foo2: '' }), true, 'Expect pass - extra data ignored'); 234 | 235 | // reset for undefined key test 236 | method = overload() 237 | .error(function() { return 'error'; }) 238 | .args(o.map({ foo: undefined, bar: Boolean })).use(a); 239 | 240 | strictEqual(method({ bar: false }), true, 'Expect pass - missing key that should be undefined is ignored'); 241 | 242 | // reset for convenience method 243 | method = overload() 244 | .error(function() { return 'error'; }) 245 | .map({ foo: String, bar: Boolean, baz: Date }).use(a); 246 | 247 | strictEqual(method(''), 'error', 'Expect error'); 248 | strictEqual(method(true), 'error', 'Expect error'); 249 | strictEqual(method(new Date()), 'error', 'Expect error'); 250 | 251 | strictEqual(method({ foo: '', bar: false }), 'error', 'Expect error - missing bar'); 252 | strictEqual(method({ foo: '', baz: new Date() }), 'error', 'Expect error - missing baz'); 253 | strictEqual(method({ bar: false, baz: new Date() }), 'error', 'Expect error - missing foo'); 254 | 255 | strictEqual(method({ foo: '', bar: false, baz: new Date() }), true, 'Expect pass - fulfilled requirements'); 256 | strictEqual(method({ foo: '', bar: false, baz: new Date(), foo2: '' }), true, 'Expect pass - extra data ignored'); 257 | }); 258 | 259 | test('fallback', function() { 260 | var method = overload() 261 | .args(String).use(function() {}) 262 | .fallback(function() { return 0; }); 263 | 264 | strictEqual(method(), 0, 'Fallback function called'); 265 | }); 266 | 267 | test('error', function() { 268 | var method = overload() 269 | .args(String).use(function() { return 0; }) 270 | .error(function() { return 'error'; }); 271 | 272 | strictEqual(!!method, true, '.error successfully chains'); 273 | strictEqual(method(''), 0, '.error chained succeeds'); 274 | strictEqual(method(), 'error', '.error chained fails'); 275 | }); 276 | 277 | test('expose', function() { 278 | var method = overload() 279 | .args(String).use(function() { return 0; }) 280 | .error(function() { return 'error'; }) 281 | .expose(); 282 | 283 | strictEqual(method(''), 0, 'Exposed function suceeds'); 284 | strictEqual(method(), 'error', 'Exposed function fails'); 285 | strictEqual(method.args, undefined, 'Exposed function is clean'); 286 | }); 287 | 288 | test('passed parameters', function() { 289 | var method = overload() 290 | .args(o.any(String, Number, Boolean)).use(function(param) { return param; }) 291 | .fallback(function() { return 'fallback'; }); 292 | 293 | equal(method('one'), 'one', 'String passed and returned'); 294 | equal(method(2), 2, 'Number passed and returned'); 295 | equal(method(true), true, 'Boolean passed and returned'); 296 | equal(method(null), 'fallback', 'No items matched and defered to the fallback'); 297 | }); 298 | 299 | test('custom', function() { 300 | var Thingy = function() {}; 301 | overload.defineType('thingy', function(val) { 302 | return (val instanceof Thingy); 303 | }); 304 | 305 | ok(o.thingy, 'Custom type added'); 306 | 307 | var a = function() { return 0; }; 308 | 309 | var method1 = overload().args(o.thingy).use(a); 310 | strictEqual(method1(new Thingy()), 0, 'Custom function works as a definition'); 311 | 312 | var method2 = overload().args(o.any(Boolean, o.thingy)).use(a); 313 | strictEqual(method2(new Thingy()), 0, 'Custom function work inside any() custom definition'); 314 | }); --------------------------------------------------------------------------------