typed-function
Advanced tools
Comparing version 0.3.1 to 0.4.0
var typed = require('../typed-function'); | ||
// create a typed function with anytype arguments | ||
var log = typed({ | ||
'string, *': function (event, data) { | ||
// create a typed function with an any type argument | ||
var log = typed('string, any', function (event, data) { | ||
console.log('event: ' + event + ', data: ' + JSON.stringify(data)); | ||
}, | ||
'*': function (data) { | ||
console.log('data: ' + JSON.stringify(data)); | ||
} | ||
}); | ||
}); | ||
// use the composed function | ||
// use the typed function | ||
log('start', {count: 2}); // output: 'event: start, data: {"count":2}' | ||
log('some data'); // output: 'data: "some data"' | ||
log('end', 'success!'); // output: 'event: start, data: "success" |
var typed = require('../typed-function'); | ||
// create a typed function | ||
var fn = typed({ | ||
'number': function (a) { | ||
return 'a is a number'; | ||
}, | ||
'number, boolean': function (a, b) { | ||
return 'a is a number, b is a boolean'; | ||
}, | ||
'number, number': function (a, b) { | ||
return 'a is a number, b is a number'; | ||
} | ||
var fn1 = typed('number, string', function (a, b) { | ||
return 'a is a number, b is a string'; | ||
}); | ||
// create a typed function with multiple types per argument (type union) | ||
var fn2 = typed('string, number | boolean', function (a, b) { | ||
return 'a is a string, b is a number or boolean'; | ||
}); | ||
// create a typed function with any type argument | ||
var fn3 = typed('string, any', function (a, b) { | ||
return 'a is a string, b can be anything'; | ||
}); | ||
// use the function | ||
console.log(fn(2, true)); // outputs 'a is a number, b is a boolean' | ||
console.log(fn(2)); // outputs 'a is a number' | ||
console.log(fn1(2, 'foo')); // outputs 'a is a number, b is a string' | ||
// calling the function with a non-supported type signature will throw an error | ||
try { | ||
fn('hello world'); | ||
fn1('hello', 'world'); | ||
} | ||
catch (err) { | ||
console.log(err.toString()); // outputs: 'TypeError: Wrong function signature' | ||
console.log(err.toString()); | ||
// outputs: TypeError: Unexpected type of argument. | ||
// Expected: number, actual: string, index: 0. | ||
} |
@@ -15,9 +15,4 @@ var typed = require('../typed-function'); | ||
// create a typed function | ||
var stringify = typed({ | ||
'Person': function (person) { | ||
return JSON.stringify(person); | ||
}, | ||
'Person,number': function (person, indentation) { | ||
return JSON.stringify(person, null, indentation); | ||
} | ||
var stringify = typed('Person', function (person) { | ||
return JSON.stringify(person); | ||
}); | ||
@@ -31,9 +26,2 @@ | ||
console.log(stringify(person, 2)); | ||
// outputs with indentation: | ||
// '{ | ||
// "name": "John", | ||
// "age": 28 | ||
// }' | ||
// calling the function with a non-supported type signature will throw an error | ||
@@ -44,3 +32,5 @@ try { | ||
catch (err) { | ||
console.log(err.toString()); // outputs: 'TypeError: Wrong function signature' | ||
console.log(err.toString()); | ||
// outputs: TypeError: Unexpected type of argument. | ||
// Expected: Person, actual: string, index: 0. | ||
} |
var typed = require('../typed-function'); | ||
// define type conversions that we want to support | ||
// order is important | ||
typed.conversions = [ | ||
@@ -28,3 +29,3 @@ { | ||
// create a typed function | ||
// create a typed function with multiple signatures | ||
// | ||
@@ -31,0 +32,0 @@ // where possible, the created function will automatically convert booleans to |
# History | ||
## 2014-12-17, version 0.4.0 | ||
- Introduced new constructor options, create a typed function as | ||
`typed([name,] signature, fn)` or `typed([name,] signatures)`. | ||
- Support for multiple types per parameter like `number | string, number'`. | ||
- Support for variable parameters like `sting, ...number'`. | ||
- Changed any type notation `'*'` to `'any'`. | ||
- Implemented detailed error messages. | ||
- Implemented option `typed.config.minify`. | ||
## 2014-11-05, version 0.3.1 | ||
@@ -10,3 +21,3 @@ | ||
- Implemented support for anytype arguments (denoted with `*`). | ||
- Implemented support for any type arguments (denoted with `*`). | ||
@@ -13,0 +24,0 @@ |
{ | ||
"name": "typed-function", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "Type checking for JavaScript functions", | ||
@@ -5,0 +5,0 @@ "author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)", |
154
README.md
@@ -1,3 +0,2 @@ | ||
typed-function | ||
================= | ||
# typed-function | ||
@@ -10,6 +9,9 @@ Type checking for JavaScript functions. | ||
- Automatic type conversion of arguments. | ||
- Compose a function from multiple sub-functions with different signatures. | ||
- Compose typed functions with multiple signatures. | ||
- Supports union types, any type, variable arguments. | ||
- Detailed error messaging. | ||
Supported environments: node.js, Chrome, Firefox, Safari, Opera, IE9+. | ||
## Load | ||
@@ -30,3 +32,18 @@ | ||
// create a typed function | ||
var fn = typed({ | ||
var fn1 = typed('number, string', function (a, b) { | ||
return 'a is a number, b is a string'; | ||
}); | ||
// create a typed function with multiple types per argument (type union) | ||
var fn2 = typed('string, number | boolean', function (a, b) { | ||
return 'a is a string, b is a number or a boolean'; | ||
}); | ||
// create a typed function with any type argument | ||
var fn3 = typed('string, any', function (a, b) { | ||
return 'a is a string, b can be anything'; | ||
}); | ||
// create a typed function with multiple signatures | ||
var fn4 = typed({ | ||
'number': function (a) { | ||
@@ -43,12 +60,14 @@ return 'a is a number'; | ||
// use the function | ||
console.log(fn(2, true)); // outputs 'a is a number, b is a boolean' | ||
console.log(fn(2)); // outputs 'a is a number' | ||
// use the functions | ||
console.log(fn1(2, 'foo')); // outputs 'a is a number, b is a string' | ||
console.log(fn4(2)); // outputs 'a is a number' | ||
// calling the function with a non-supported type signature will throw an error | ||
try { | ||
fn('hello world'); | ||
fn4('hello world'); | ||
} | ||
catch (err) { | ||
console.log(err.toString()); // outputs: 'TypeError: Wrong function signature' | ||
console.log(err.toString()); | ||
// outputs: TypeError: Unexpected type of argument. | ||
// Expected: number, actual: string, index: 0. | ||
} | ||
@@ -58,11 +77,24 @@ ``` | ||
## Performance | ||
## Types | ||
Type checking input arguments adds some overhead to a function. For very small | ||
functions this overhead can be larger than the function execution itself is, | ||
but for any non-trivial function the overhead is typically small to neglectable. | ||
You need to keep in mind though that you probably would have to do the type | ||
checking done by `typed-function` anyway. | ||
typed-function has the following built-in types: | ||
- `null` | ||
- `boolean` | ||
- `number` | ||
- `string` | ||
- `function` | ||
- `Array` | ||
- `Date` | ||
- `RegExp` | ||
- `Object` | ||
The following type expressions are supported: | ||
- Multiple parameters: `string, number, function` | ||
- Union types: `number | string` | ||
- Variable parameters: `...number` | ||
- Any type: `any` | ||
## API | ||
@@ -72,5 +104,8 @@ | ||
A function is constructed as: | ||
A typed function can be constructed as: | ||
```js | ||
typed(signature: string, fn: function) : function | ||
typed(name: string, signature: string, fn: function) : function | ||
typed(signatures: Object.<string, function>) : function | ||
@@ -82,45 +117,41 @@ typed(name: string, signatures: Object.<string, function>) : function | ||
- `typed.types: Object` | ||
A map with the object types as key and a type checking test as value. | ||
Custom types can be added like: | ||
- `typed.types: Object` | ||
```js | ||
function Person(...) { | ||
... | ||
} | ||
A map with the object types as key and a type checking test as value. | ||
Custom types can be added like: | ||
typed.types['Person'] = function (x) { | ||
return x instanceof Person; | ||
}; | ||
```js | ||
function Person(...) { | ||
... | ||
} | ||
typed.types['Person'] = function (x) { | ||
return x instanceof Person; | ||
}; | ||
``` | ||
- `typed.conversions: Array` | ||
An Array with built-in conversions. Empty by default. Can be used for example | ||
to defined conversions from `boolean` to `number`. For example: | ||
- `typed.conversions: Array` | ||
```js | ||
typed.conversions.push({ | ||
from: 'boolean', | ||
to: 'number', | ||
convert: function (x) { | ||
return +x; | ||
}); | ||
``` | ||
An Array with built-in conversions. Empty by default. Can be used for example | ||
to defined conversions from `boolean` to `number`. For example: | ||
### Types | ||
```js | ||
typed.conversions.push({ | ||
from: 'boolean', | ||
to: 'number', | ||
convert: function (x) { | ||
return +x; | ||
}); | ||
``` | ||
typed-function has the following built-in types: | ||
- `typed.config: Object` | ||
- `null` | ||
- `boolean` | ||
- `number` | ||
- `string` | ||
- `function` | ||
- `Array` | ||
- `Date` | ||
- `RegExp` | ||
- `Object` | ||
- `*` (anytype) | ||
An object with configuration options for typed-function: | ||
- `minify: boolean` | ||
If true (default), the functions are generated from minified code. | ||
If false the typed-functions have a nicely readable .toString() source. | ||
### Output | ||
@@ -130,4 +161,5 @@ | ||
- A `toString()` function which returns well readable code, giving insight in | ||
what the function exactly does. | ||
- A function `toString`. When `typed.config.minify` is set to `true` (is `false` | ||
by default), the `toString` function will return well readable code which can | ||
be used to see what the function exactly does. For debugging purposes. | ||
- A property `signatures`, which holds a map with the (normalized) | ||
@@ -137,2 +169,11 @@ signatures as key and the original sub-functions as value. | ||
## Performance | ||
Type checking input arguments adds some overhead to a function. For very small | ||
functions this overhead can be larger than the function execution itself is, | ||
but for any non-trivial function the overhead is typically small to neglectable. | ||
You need to keep in mind though that you probably would have to do the type | ||
checking done by `typed-function` anyway. | ||
## Roadmap | ||
@@ -143,7 +184,6 @@ | ||
- Extend function signatures: | ||
- Any type arguments like `'*, boolean'` | ||
- Ellipsis like `'string, ...'` | ||
- Optional arguments like `'number?, array'` | ||
- Multiple types per argument like `number | string, number'` | ||
- Detailed error messages. | ||
- Optional arguments like `'[number], array'` or like `number=, array` | ||
- Nullable arguments like `'?Object'` | ||
- Be able to merge typed functions into a new typed function, like | ||
`fn1 = merged(fn2, fn3)`. | ||
- Create a good benchmark, to get insight in the overhead. | ||
@@ -156,3 +196,3 @@ - Allow conversions not to be able to convert any input (for example string to | ||
- Extend function signatures: | ||
- Constants like `'"linear" | "cubic"'`. | ||
- Constants like `'"linear" | "cubic"'`, `'0..10'`, etc. | ||
- Object definitions like `'{name: string, age: number}'` | ||
@@ -159,0 +199,0 @@ - Object definitions like `'Object.<string, Person>'` |
@@ -5,48 +5,25 @@ // test parse | ||
describe('parse', function() { | ||
describe('typed-function', function() { | ||
it('should compose an empty function', function() { | ||
var fn = typed({}); | ||
it('should throw an error when not providing any signatures', function() { | ||
assert.throws(function () { | ||
fn(); | ||
}, /TypeError: Wrong function signature/); | ||
assert(fn.signatures instanceof Object); | ||
assert.deepEqual(fn.signatures, []); | ||
typed({}); | ||
}, /Error: No signatures provided/); | ||
}); | ||
it('should compose a function with zero arguments', function() { | ||
var signatures = { | ||
'': function () { | ||
return 'noargs'; | ||
} | ||
}; | ||
var fn = typed(signatures); | ||
assert.equal(fn(), 'noargs'); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 1); | ||
assert.strictEqual(fn.signatures[''], signatures['']); | ||
}); | ||
it('should create a named function', function() { | ||
var fn = typed('myFunction', { | ||
'': function () { | ||
return 'noargs'; | ||
'string': function (str) { | ||
return 'foo'; | ||
} | ||
}); | ||
assert.equal(fn(), 'noargs'); | ||
assert.equal(fn('bar'), 'foo'); | ||
assert.equal(fn.name, 'myFunction'); | ||
}); | ||
it('should compose a function with one argument', function() { | ||
it('should compose a function with zero arguments', function() { | ||
var signatures = { | ||
'number': function (value) { | ||
return 'number:' + value; | ||
}, | ||
'string': function (value) { | ||
return 'string:' + value; | ||
}, | ||
'boolean': function (value) { | ||
return 'boolean:' + value; | ||
'': function () { | ||
return 'noargs'; | ||
} | ||
@@ -56,117 +33,31 @@ }; | ||
assert.equal(fn(2), 'number:2'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(false), 'boolean:false'); | ||
assert.equal(fn(), 'noargs'); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.strictEqual(fn.signatures['number'], signatures['number']); | ||
assert.strictEqual(fn.signatures['string'], signatures['string']); | ||
assert.strictEqual(fn.signatures['boolean'], signatures['boolean']); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 1); | ||
assert.strictEqual(fn.signatures[''], signatures['']); | ||
}); | ||
it('should compose a function with multiple arguments', function() { | ||
var signatures = { | ||
'number': function (value) { | ||
return 'number:' + value; | ||
}, | ||
'string': function (value) { | ||
return 'string:' + value; | ||
}, | ||
'number, boolean': function (a, b) { // mind space after the comma, should be normalized by composer | ||
return 'number,boolean:' + a + ',' + b; | ||
} | ||
}; | ||
var fn = typed(signatures); | ||
it('should create a typed function with one argument', function() { | ||
var fn = typed('string', function () { | ||
return 'string'; | ||
}); | ||
assert.equal(fn(2), 'number:2'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(2, false), 'number,boolean:2,false'); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.strictEqual(fn.signatures['number'], signatures['number']); | ||
assert.strictEqual(fn.signatures['string'], signatures['string']); | ||
assert.strictEqual(fn.signatures['number,boolean'], signatures['number, boolean']); | ||
assert.equal(fn('hi'), 'string'); | ||
}); | ||
describe('anytype', function () { | ||
it('should compose a function with one anytype argument', function() { | ||
var fn = typed({ | ||
'*': function (value) { | ||
return 'anytype:' + value; | ||
}, | ||
'string': function (value) { | ||
return 'string:' + value; | ||
}, | ||
'boolean': function (value) { | ||
return 'boolean:' + value; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.equal(fn(2), 'anytype:2'); | ||
assert.equal(fn([1,2,3]), 'anytype:1,2,3'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(false), 'boolean:false'); | ||
it('should create a typed function with two arguments', function() { | ||
var fn = typed('string, boolean', function () { | ||
return 'foo'; | ||
}); | ||
it('should compose a function with multiple anytype arguments (1)', function() { | ||
var fn = typed({ | ||
'*,boolean': function () { | ||
return 'anytype,boolean'; | ||
}, | ||
'*,string': function () { | ||
return 'anytype,string'; | ||
} | ||
}); | ||
assert.equal(fn('hi', true), 'foo'); | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 2); | ||
assert.equal(fn([],true), 'anytype,boolean'); | ||
assert.equal(fn(2,'foo'), 'anytype,string'); | ||
assert.throws(function () {fn([], new Date())}, /Wrong function signature/) | ||
assert.throws(function () {fn(2, 2)}, /Wrong function signature/) | ||
it('should create a named, typed function', function() { | ||
var fn = typed('myFunction', 'string, boolean', function () { | ||
return 'noargs'; | ||
}); | ||
it('should compose a function with multiple anytype arguments (2)', function() { | ||
var fn = typed({ | ||
'*,boolean': function () { | ||
return 'anytype,boolean'; | ||
}, | ||
'*,number': function () { | ||
return 'anytype,number'; | ||
}, | ||
'string,*': function () { | ||
return 'string,anytype'; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.equal(fn([],true), 'anytype,boolean'); | ||
assert.equal(fn([],2), 'anytype,number'); | ||
assert.equal(fn('foo', 2), 'string,anytype'); | ||
assert.throws(function () {fn([], new Date())}, /Wrong function signature/) | ||
assert.throws(function () {fn([], 'foo')}, /Wrong function signature/) | ||
}); | ||
it('should compose a function with multiple anytype arguments (3)', function() { | ||
var fn = typed({ | ||
'string,*': function () { | ||
return 'string,anytype'; | ||
}, | ||
'*': function () { | ||
return 'anytype'; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 2); | ||
assert.equal(fn('foo', 2), 'string,anytype'); | ||
assert.equal(fn('foo'), 'anytype'); | ||
assert.equal(fn([]), 'anytype'); | ||
assert.throws(function () {fn([], 'foo')}, /Wrong function signature/) | ||
}); | ||
assert.equal(fn('hi', true), 'noargs'); | ||
assert.equal(fn.name, 'myFunction'); | ||
}); | ||
@@ -198,6 +89,6 @@ | ||
assert.throws(function () {fn(new Date())}, /TypeError: Wrong function signature/); | ||
assert.throws(function () {fn(new Date())}, /TypeError: Unexpected type of argument. Expected: number, actual: Date, index: 0./); | ||
}); | ||
it('should throw an error when providing a wrong number of arguments', function() { | ||
it('should throw an error when providing a Wrong function signature', function() { | ||
var fn = typed({ | ||
@@ -209,3 +100,3 @@ 'number': function (value) { | ||
assert.throws(function () {fn(1, 2)}, /TypeError: Wrong number of arguments/); | ||
assert.throws(function () {fn(1, 2)}, /TypeError: Too many arguments. Expected: 1, actual: 2./); | ||
}); | ||
@@ -241,4 +132,332 @@ | ||
describe('conversions' , function () { | ||
describe('configuration', function () { | ||
it('option minify should be true by default', function () { | ||
assert.equal(typed.config.minify, true) | ||
}); | ||
it('should configure option minify', function () { | ||
var orig = typed.config.minify; | ||
typed.config.minify = true; | ||
var fn1 = typed('string', function (str) { | ||
return 'string'; | ||
}); | ||
var len1 = fn1.toString().length; | ||
typed.config.minify = false; | ||
var fn2 = typed('string', function (str) { | ||
return 'string'; | ||
}); | ||
var len2 = fn2.toString().length; | ||
assert.equal(fn1('foo'), 'string'); | ||
assert.equal(fn2('foo'), 'string'); | ||
assert(len2 > len1); | ||
// restore original configuration | ||
typed.config.minify = orig; | ||
}); | ||
}); | ||
describe('multiple types', function () { | ||
it('should create a typed function with multiple types per argument', function() { | ||
var fn = typed('number | boolean', function (arg) { | ||
return typeof arg; | ||
}); | ||
assert.equal(fn(true), 'boolean'); | ||
assert.equal(fn(2), 'number'); | ||
assert.throws(function () {fn('string')}, /TypeError: Unexpected type of argument. Expected: number or boolean, actual: string, index: 0./); | ||
}); | ||
}); | ||
describe('variable arguments', function () { | ||
it('should create a typed function with variable arguments', function() { | ||
var sum = typed('...number', function (values) { | ||
assert(Array.isArray(values)); | ||
var sum = 0; | ||
for (var i = 0; i < values.length; i++) { | ||
sum += values[i]; | ||
} | ||
return sum; | ||
}); | ||
assert.equal(sum(2), 2); | ||
assert.equal(sum(2,3,4), 9); | ||
assert.throws(function () {sum()}, /TypeError: Too few arguments. Expected: number, index: 0./); | ||
assert.throws(function () {sum(true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 0./); | ||
assert.throws(function () {sum('string')}, /TypeError: Unexpected type of argument. Expected: number, actual: string, index: 0./); | ||
assert.throws(function () {sum(2, 'string')}, /TypeError: Unexpected type of argument. Expected: number, actual: string, index: 1./); | ||
assert.throws(function () {sum(2, 3, 'string')}, /TypeError: Unexpected type of argument. Expected: number, actual: string, index: 2./); | ||
}); | ||
it('should create a typed function with variable arguments (2)', function() { | ||
var fn = typed('string, ...number', function (str, values) { | ||
assert.equal(typeof str, 'string'); | ||
assert(Array.isArray(values)); | ||
return str + ': ' + values.join(', '); | ||
}); | ||
assert.equal(fn('foo', 2), 'foo: 2'); | ||
assert.equal(fn('foo', 2, 4), 'foo: 2, 4'); | ||
assert.throws(function () {fn(2, 4)}, /TypeError: Unexpected type of argument. Expected: string, actual: number, index: 0./); | ||
assert.throws(function () {fn('string')}, /TypeError: Too few arguments. Expected: number, index: 1./); | ||
assert.throws(function () {fn('string', 'string')}, /TypeError: Unexpected type of argument. Expected: number, actual: string, index: 1./); | ||
}); | ||
it('should create a typed function with any type arguments (1)', function() { | ||
var fn = typed('string, ...any', function (str, values) { | ||
assert.equal(typeof str, 'string'); | ||
assert(Array.isArray(values)); | ||
return str + ': ' + values.join(', '); | ||
}); | ||
assert.equal(fn('foo', 2), 'foo: 2'); | ||
assert.equal(fn('foo', 2, true, 'bar'), 'foo: 2, true, bar'); | ||
assert.equal(fn('foo', 'bar'), 'foo: bar'); | ||
assert.throws(function () {fn(2, 4)}, /TypeError: Unexpected type of argument. Expected: string, actual: number, index: 0./); | ||
assert.throws(function () {fn('string')}, /TypeError: Too few arguments. Expected: any, index: 1./); | ||
}); | ||
it('should create a typed function with implicit any type arguments', function() { | ||
var fn = typed('string, ...', function (str, values) { | ||
assert.equal(typeof str, 'string'); | ||
assert(Array.isArray(values)); | ||
return str + ': ' + values.join(', '); | ||
}); | ||
assert.equal(fn('foo', 2), 'foo: 2'); | ||
assert.equal(fn('foo', 2, true, 'bar'), 'foo: 2, true, bar'); | ||
assert.equal(fn('foo', 'bar'), 'foo: bar'); | ||
assert.throws(function () {fn(2, 4)}, /TypeError: Unexpected type of argument. Expected: string, actual: number, index: 0./); | ||
assert.throws(function () {fn('string')}, /TypeError: Too few arguments. Expected: any, index: 1./); | ||
}); | ||
it('should create a typed function with any type arguments (2)', function() { | ||
var fn = typed('any, ...number', function (any, values) { | ||
assert(Array.isArray(values)); | ||
return any + ': ' + values.join(', '); | ||
}); | ||
assert.equal(fn('foo', 2), 'foo: 2'); | ||
assert.equal(fn(1, 2, 4), '1: 2, 4'); | ||
assert.equal(fn(null, 2, 4), 'null: 2, 4'); | ||
assert.throws(function () {fn('string')}, /TypeError: Too few arguments. Expected: number, index: 1./); | ||
assert.throws(function () {fn('string', 'string')}, /TypeError: Unexpected type of argument. Expected: number, actual: string, index: 1./); | ||
}); | ||
it('should create a typed function with union type arguments', function() { | ||
var fn = typed('...number|string', function (values) { | ||
assert(Array.isArray(values)); | ||
return values; | ||
}); | ||
strictEqualArray(fn(2,3,4), [2,3,4]); | ||
strictEqualArray(fn('a','b','c'), ['a','b','c']); | ||
strictEqualArray(fn('a',2,'c',3), ['a',2,'c',3]); | ||
assert.throws(function () {fn()}, /TypeError: Too few arguments. Expected: number or string, index: 0./); | ||
assert.throws(function () {fn('string', true)}, /TypeError: Unexpected type of argument. Index: 1. Expected: string | number/); | ||
assert.throws(function () {fn(2, false)}, /TypeError: Unexpected type of argument. Index: 1. Expected: string | number/); | ||
assert.throws(function () {fn(2, 3, false)}, /TypeError: Unexpected type of argument. Index: 2. Expected: string | number/); | ||
}); | ||
it('should create a composed function with variable arguments', function() { | ||
var fn = typed({ | ||
'string, ...number': function (str, values) { | ||
assert.equal(typeof str, 'string'); | ||
assert(Array.isArray(values)); | ||
return str + ': ' + values.join(', '); | ||
}, | ||
'...boolean': function (values) { | ||
assert(Array.isArray(values)); | ||
return 'booleans'; | ||
} | ||
}); | ||
assert.equal(fn('foo', 2), 'foo: 2'); | ||
assert.equal(fn('foo', 2, 4), 'foo: 2, 4'); | ||
assert.equal(fn(true, false, false), 'booleans'); | ||
// FIXME: error should be Expected: string or boolean | ||
assert.throws(function () {fn(2, 4)}, /TypeError: Unexpected type of argument. Expected: boolean, actual: number, index: 0./); | ||
assert.throws(function () {fn('string')}, /TypeError: Too few arguments. Expected: number, index: 1./); | ||
assert.throws(function () {fn('string', true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 1./); | ||
}); | ||
it('should throw an error in case of unexpected variable arguments', function() { | ||
assert.throws(function () { | ||
typed('...number, string', function () {}); | ||
}, /SyntaxError: Unexpected variable arguments operator "..."/); | ||
}); | ||
}); | ||
describe('compose', function () { | ||
it('should create a composed function with multiple types per argument', function() { | ||
var fn = typed({ | ||
'string | number, boolean': function () {return 'A';}, | ||
'boolean, boolean | number': function () {return 'B';}, | ||
'string': function () {return 'C';} | ||
}); | ||
assert.equal(fn('str', false), 'A'); | ||
assert.equal(fn(2, true), 'A'); | ||
assert.equal(fn(false, true), 'B'); | ||
assert.equal(fn(false, 2), 'B'); | ||
assert.equal(fn('str'), 'C'); | ||
// FIXME: should return correct error message | ||
assert.throws(function () {fn()}, /TypeError: Too few arguments. Expected: string or number or boolean, index: 0./); | ||
assert.throws(function () {fn(1,2,3)}, /TypeError: Unexpected type of argument. Expected: boolean, actual: number, index: 1./); | ||
assert.throws(function () {fn('str', 2)}, /TypeError: Unexpected type of argument. Expected: boolean, actual: number, index: 1./); | ||
assert.throws(function () {fn(true, 'str')},/TypeError: Unexpected type of argument. Expected: boolean or number, actual: string, index: 1./); | ||
assert.throws(function () {fn(2, 3)}, /TypeError: Unexpected type of argument. Expected: boolean, actual: number, index: 1./); | ||
assert.throws(function () {fn(2, 'str')}, /TypeError: Unexpected type of argument. Expected: boolean, actual: string, index: 1./); | ||
}); | ||
// TODO: test whether the constructor throws errors when providing wrong arguments to typed(...) | ||
it('should compose a function with one argument', function() { | ||
var signatures = { | ||
'number': function (value) { | ||
return 'number:' + value; | ||
}, | ||
'string': function (value) { | ||
return 'string:' + value; | ||
}, | ||
'boolean': function (value) { | ||
return 'boolean:' + value; | ||
} | ||
}; | ||
var fn = typed(signatures); | ||
assert.equal(fn(2), 'number:2'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(false), 'boolean:false'); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.strictEqual(fn.signatures['number'], signatures['number']); | ||
assert.strictEqual(fn.signatures['string'], signatures['string']); | ||
assert.strictEqual(fn.signatures['boolean'], signatures['boolean']); | ||
}); | ||
it('should compose a function with multiple arguments', function() { | ||
var signatures = { | ||
'number': function (value) { | ||
return 'number:' + value; | ||
}, | ||
'string': function (value) { | ||
return 'string:' + value; | ||
}, | ||
'number, boolean': function (a, b) { // mind space after the comma, should be normalized by composer | ||
return 'number,boolean:' + a + ',' + b; | ||
} | ||
}; | ||
var fn = typed(signatures); | ||
assert.equal(fn(2), 'number:2'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(2, false), 'number,boolean:2,false'); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.strictEqual(fn.signatures['number'], signatures['number']); | ||
assert.strictEqual(fn.signatures['string'], signatures['string']); | ||
assert.strictEqual(fn.signatures['number,boolean'], signatures['number, boolean']); | ||
}); | ||
}); | ||
describe('any type', function () { | ||
it('should compose a function with one any type argument', function() { | ||
var fn = typed({ | ||
'any': function (value) { | ||
return 'any:' + value; | ||
}, | ||
'string': function (value) { | ||
return 'string:' + value; | ||
}, | ||
'boolean': function (value) { | ||
return 'boolean:' + value; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.equal(fn(2), 'any:2'); | ||
assert.equal(fn([1,2,3]), 'any:1,2,3'); | ||
assert.equal(fn('foo'), 'string:foo'); | ||
assert.equal(fn(false), 'boolean:false'); | ||
}); | ||
it('should compose a function with multiple any type arguments (1)', function() { | ||
var fn = typed({ | ||
'any,boolean': function () { | ||
return 'any,boolean'; | ||
}, | ||
'any,string': function () { | ||
return 'any,string'; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 2); | ||
assert.equal(fn([],true), 'any,boolean'); | ||
assert.equal(fn(2,'foo'), 'any,string'); | ||
assert.throws(function () {fn([], new Date())}, /TypeError: Unexpected type of argument. Expected: boolean or string, actual: Date, index: 1./); | ||
assert.throws(function () {fn(2, 2)}, /TypeError: Unexpected type of argument. Expected: boolean or string, actual: number, index: 1./); | ||
assert.throws(function () {fn(2)}, /TypeError: Too few arguments. Expected: boolean or string, index: 1./); | ||
}); | ||
it('should compose a function with multiple any type arguments (2)', function() { | ||
var fn = typed({ | ||
'any,boolean': function () { | ||
return 'any,boolean'; | ||
}, | ||
'any,number': function () { | ||
return 'any,number'; | ||
}, | ||
'string,any': function () { | ||
return 'string,any'; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 3); | ||
assert.equal(fn([],true), 'any,boolean'); | ||
assert.equal(fn([],2), 'any,number'); | ||
assert.equal(fn('foo', 2), 'string,any'); | ||
assert.throws(function () {fn([], new Date())}, /TypeError: Unexpected type of argument. Expected: boolean or number, actual: Date, index: 1./); | ||
assert.throws(function () {fn([], 'foo')}, /TypeError: Unexpected type of argument. Expected: boolean or number, actual: string, index: 1./) | ||
}); | ||
it('should compose a function with multiple any type arguments (3)', function() { | ||
var fn = typed({ | ||
'string,any': function () { | ||
return 'string,any'; | ||
}, | ||
'any': function () { | ||
return 'any'; | ||
} | ||
}); | ||
assert(fn.signatures instanceof Object); | ||
assert.strictEqual(Object.keys(fn.signatures).length, 2); | ||
assert.equal(fn('foo', 2), 'string,any'); | ||
assert.equal(fn('foo'), 'any'); | ||
assert.equal(fn([]), 'any'); | ||
assert.throws(function () {fn([], 'foo')}, /TypeError: Too many arguments. Expected: 1, actual: 2./) | ||
}); | ||
}); | ||
describe('conversions', function () { | ||
before(function () { | ||
@@ -295,2 +514,88 @@ typed.conversions = [ | ||
it('should add conversions to a function with variable arguments (1)', function() { | ||
var sum = typed('...number', function (values) { | ||
assert(Array.isArray(values)); | ||
var sum = 0; | ||
for (var i = 0; i < values.length; i++) { | ||
sum += values[i]; | ||
} | ||
return sum; | ||
}); | ||
assert.equal(sum(2,3,4), 9); | ||
// TODO | ||
//assert.equal(sum(2,true,4), 7); | ||
//assert.equal(sum(1,2,false), 3); | ||
//assert.equal(sum(1,2,true), 4); | ||
//assert.equal(sum(true,1,2), 4); | ||
assert.equal(sum(true,false, true), 2); | ||
}); | ||
it('should add conversions to a function with variable arguments (2)', function() { | ||
var sum = typed('string, ...number', function (name, values) { | ||
assert.equal(typeof name, 'string'); | ||
assert(Array.isArray(values)); | ||
var sum = 0; | ||
for (var i = 0; i < values.length; i++) { | ||
sum += values[i]; | ||
} | ||
return sum; | ||
}); | ||
assert.equal(sum('foo', 2,3,4), 9); | ||
assert.equal(sum('foo', 2,true,4), 7); | ||
assert.equal(sum('foo', 1,2,false), 3); | ||
assert.equal(sum('foo', 1,2,true), 4); | ||
assert.equal(sum('foo', true,1,2), 4); | ||
assert.equal(sum('foo', true,false, true), 2); | ||
assert.equal(sum(123, 2,3), 5); | ||
assert.equal(sum(false, 2,3), 5); | ||
}); | ||
it('should add conversions to a function with variable arguments in a non-conflicting way', function() { | ||
// note that a series of booleans can be converted to numbers, but a single | ||
// boolean should call the second signature `boolean` | ||
var fn = typed({ | ||
'...number': function (values) { | ||
assert(Array.isArray(values)); | ||
var sum = 0; | ||
for (var i = 0; i < values.length; i++) { | ||
sum += values[i]; | ||
} | ||
return sum; | ||
}, | ||
'boolean': function (value) { | ||
assert.equal(typeof value, 'boolean'); | ||
return 'boolean'; | ||
} | ||
}); | ||
assert.equal(fn(2,3,4), 9); | ||
assert.equal(fn(2,true,4), 7); | ||
assert.equal(fn(1,2,false), 3); | ||
assert.equal(fn(1,2,true), 4); | ||
assert.equal(fn(true,1,2), 4); | ||
assert.equal(fn(true,false, true), 2); | ||
assert.equal(fn(2,3), 5); | ||
assert.equal(fn(false), 'boolean'); | ||
assert.equal(fn(true), 'boolean'); | ||
}); | ||
it('should add conversions to a function with variable and union arguments', function() { | ||
var fn = typed({ | ||
'...string | number': function (values) { | ||
assert(Array.isArray(values)); | ||
return values; | ||
} | ||
}); | ||
strictEqualArray(fn(2,3,4), [2,3,4]); | ||
strictEqualArray(fn(2,true,4), [2,1,4]); | ||
strictEqualArray(fn(2,'str'), [2,'str']); | ||
strictEqualArray(fn('str', true, false), ['str', 1, 0]); | ||
strictEqualArray(fn('str', 2, false), ['str', 2, 0]); | ||
assert.throws(function () {fn(new Date(), '2')}, /TypeError: Unexpected type of argument. Expected: string or number, actual: Date, index: 0./) | ||
}); | ||
it('should add non-conflicting conversions to a function with one argument', function() { | ||
@@ -314,5 +619,95 @@ var fn = typed({ | ||
}); | ||
it('should add non-conflicting conversions to a function with one argument', function() { | ||
var fn = typed({ | ||
'boolean': function (a) { | ||
return a; | ||
} | ||
}); | ||
// booleans should be converted to number | ||
assert.equal(fn(false), 0); | ||
assert.equal(fn(true), 1); | ||
}); | ||
}); | ||
describe('errors', function () { | ||
it('should give correct error in case of too few arguments', function() { | ||
var fn = typed('string, boolean', function () {}); | ||
assert.throws(function () {fn()}, /TypeError: Too few arguments. Expected: string, index: 0./); | ||
assert.throws(function () {fn('foo')}, /TypeError: Too few arguments. Expected: boolean, index: 1./); | ||
}); | ||
it('should give correct error in case of too few arguments (varArgs)', function() { | ||
var fn = typed('...string', function () {}); | ||
assert.throws(function () {fn()}, /TypeError: Too few arguments. Expected: string, index: 0./); | ||
}); | ||
it('should give correct error in case of too few arguments (varArgs) (2)', function() { | ||
var fn = typed('boolean, ...string', function () {}); | ||
assert.throws(function () {fn()}, /TypeError: Too few arguments. Expected: boolean, index: 0./); | ||
assert.throws(function () {fn(true)}, /TypeError: Too few arguments. Expected: string, index: 1./); | ||
}); | ||
it('should give correct error in case of too many arguments', function() { | ||
var fn = typed('string, boolean', function () {}); | ||
assert.throws(function () {fn('foo', true, 2)}, /TypeError: Too many arguments. Expected: 2, actual: 3./); | ||
assert.throws(function () {fn('foo', true, 2, 1)}, /TypeError: Too many arguments. Expected: 2, actual: 4./); | ||
}); | ||
it('should give correct error in case of wrong type of argument', function() { | ||
var fn = typed('boolean', function () {}); | ||
assert.throws(function () {fn('foo')}, /TypeError: Unexpected type of argument. Expected: boolean, actual: string, index: 0./); | ||
}); | ||
it('should give correct error in case of wrong type of argument (union args)', function() { | ||
var fn = typed('boolean | string | Date', function () {}); | ||
assert.throws(function () {fn(2)}, /TypeError: Unexpected type of argument. Expected: boolean or string or Date, actual: number, index: 0./); | ||
}); | ||
it('should give correct error in case of wrong type of argument (varArgs)', function() { | ||
var fn = typed('...number', function () {}); | ||
assert.throws(function () {fn(true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 0./); | ||
assert.throws(function () {fn(2, true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 1./); | ||
assert.throws(function () {fn(2, 3, true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 2./); | ||
}); | ||
it('should give correct error in case of wrong type of argument (nested varArgs)', function() { | ||
var fn = typed('string, ...number', function () {}); | ||
assert.throws(function () {fn(true)}, /TypeError: Unexpected type of argument. Expected: string, actual: boolean, index: 0./); | ||
assert.throws(function () {fn('foo', true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 1./); | ||
assert.throws(function () {fn('foo', 2, true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 2./); | ||
assert.throws(function () {fn('foo', 2, 3, true)}, /TypeError: Unexpected type of argument. Expected: number, actual: boolean, index: 3./); | ||
}); | ||
it('should give correct error in case of wrong type of argument (union and varArgs)', function() { | ||
var fn = typed('...number|boolean', function () {}); | ||
assert.throws(function () {fn('foo')}, /TypeError: Unexpected type of argument. Expected: number or boolean, actual: string, index: 0./); | ||
assert.throws(function () {fn(2, 'foo')}, /TypeError: Unexpected type of argument. Expected: number or boolean, actual: string, index: 1./); | ||
assert.throws(function () {fn(2, true, 'foo')}, /TypeError: Unexpected type of argument. Expected: number or boolean, actual: string, index: 2./); | ||
}); | ||
}); | ||
// TODO: test compose.tests | ||
// TODO: test the generated .signatures object on a typed function | ||
}); | ||
function strictEqualArray(a, b) { | ||
assert.strictEqual(a.length, b.length); | ||
for (var i = 0; i < a.length; a++) { | ||
assert.strictEqual(a[i], b[i]); | ||
} | ||
} |
@@ -25,7 +25,7 @@ /** | ||
// order types | ||
// anytype (*) will be ordered last, and then object, as other types may be | ||
// an object too. | ||
// any type ('any') will be ordered last, and object as second last (as other types | ||
// may be an object as well, like Array) | ||
function compareTypes(a, b) { | ||
if (a === '*') return 1; | ||
if (b === '*') return -1; | ||
if (a === 'any') return 1; | ||
if (b === 'any') return -1; | ||
@@ -38,56 +38,153 @@ if (a === 'Object') return 1; | ||
// order numbers | ||
function compareNumbers(a, b) { | ||
return a > b; | ||
/** | ||
* Merge multiple objects. | ||
* @param {...Object} args | ||
*/ | ||
function merge (args) { | ||
var obj = {}; | ||
for (var i = 0; i < arguments.length; i++) { | ||
var o = arguments[i]; | ||
for (var prop in o) { | ||
if (o.hasOwnProperty(prop)) { | ||
obj[prop] = o[prop]; | ||
} | ||
} | ||
} | ||
return obj; | ||
} | ||
/** | ||
* Collection with function definitions (local shortcuts to functions) | ||
* Get a type test function for a specific data type | ||
* @param {string} type A data type like 'number' or 'string' | ||
* @returns {function(obj: *) : boolean} Returns a type testing function. | ||
* Throws an error for an unknown type. | ||
*/ | ||
function getTypeTest(type) { | ||
var test = typed.types[type]; | ||
if (!test) { | ||
var matches = Object.keys(typed.types) | ||
.filter(function (t) { | ||
return t.toLowerCase() == type.toLowerCase(); | ||
}) | ||
.map(function (t) { | ||
return '"' + t + '"'; | ||
}); | ||
throw new Error('Unknown type "' + type + '"' + | ||
(matches.length ? ('. Did you mean ' + matches.join(', or ') + '?') : '')); | ||
} | ||
return test; | ||
} | ||
/** | ||
* Collection with function references (local shortcuts to functions) | ||
* @constructor | ||
* @param {string} [name='refs'] Optional name for the refs, used to generate | ||
* JavaScript code | ||
*/ | ||
function Defs() {} | ||
function Refs(name) { | ||
this.name = name || 'refs'; | ||
this.categories = {}; | ||
} | ||
/** | ||
* Add a function definition. | ||
* Add a function reference. | ||
* @param {function} fn | ||
* @param {string} [type='fn'] | ||
* @param {string} [category='fn'] A function category, like 'fn' or 'signature' | ||
* @returns {string} Returns the function name, for example 'fn0' or 'signature2' | ||
*/ | ||
Defs.prototype.add = function (fn, type) { | ||
type = type || 'fn'; | ||
if (!this[type]) this[type] = []; | ||
Refs.prototype.add = function (fn, category) { | ||
var cat = category || 'fn'; | ||
if (!this.categories[cat]) this.categories[cat] = []; | ||
var index = this[type].indexOf(fn); | ||
var index = this.categories[cat].indexOf(fn); | ||
if (index == -1) { | ||
index = this[type].length; | ||
this[type].push(fn); | ||
index = this.categories[cat].length; | ||
this.categories[cat].push(fn); | ||
} | ||
return type + index; | ||
return cat + index; | ||
}; | ||
/** | ||
* Create code lines for all definitions | ||
* @param [name='defs'] | ||
* @returns {Array} Returns the code lines containing all function definitions | ||
* Create code lines for all function references | ||
* @returns {string} Returns the code containing all function references | ||
*/ | ||
Defs.prototype.code = function (name) { | ||
var me = this; | ||
Refs.prototype.toCode = function () { | ||
var code = []; | ||
name = name || 'defs'; | ||
var path = this.name + '.categories'; | ||
var categories = this.categories; | ||
Object.keys(this).forEach(function (type) { | ||
var def = me[type]; | ||
def.forEach(function (def, index) { | ||
code.push('var ' + type + index + ' = ' + name + '[\'' + type + '\'][' + index + '];'); | ||
Object.keys(categories).forEach(function (cat) { | ||
categories[cat].forEach(function (ref, index) { | ||
code.push('var ' + cat + index + ' = ' + path + '[\'' + cat + '\'][' + index + '];'); | ||
}); | ||
}); | ||
return code.join('\n'); | ||
}; | ||
return code; | ||
/** | ||
* A function parameter | ||
* @param {string | string[] | Param} types A parameter type like 'string', | ||
* 'number | boolean' | ||
* @param {boolean} [varArgs=false] Variable arguments if true | ||
* @constructor | ||
*/ | ||
function Param(types, varArgs) { | ||
// parse the types, can be a string with types separated by pipe characters | | ||
if (typeof types === 'string') { | ||
this.types = types.split('|').map(function (type) { | ||
return type.trim(); | ||
}); | ||
} | ||
else if (Array.isArray(types)) { | ||
this.types = types; | ||
} | ||
else if (types instanceof Param) { | ||
return types.clone(); | ||
} | ||
else { | ||
throw new Error('String or Array expected'); | ||
} | ||
// parse variable arguments operator (ellipses '...number') | ||
if (this.types[0] !== undefined && this.types[0].substring(0, 3) == '...') { | ||
this.types[0] = this.types[0].substring(3) || 'any'; | ||
this.varArgs = true; | ||
} | ||
else { | ||
this.varArgs = varArgs || false; | ||
} | ||
// check for any type arguments | ||
this.anyType = this.types.some(function (type) { | ||
return type == 'any'; | ||
}); | ||
} | ||
/** | ||
* Create a clone of this param | ||
* @returns {Param} A cloned version of this param | ||
*/ | ||
Param.prototype.clone = function () { | ||
return new Param(this.types.slice(), this.varArgs); | ||
}; | ||
/** | ||
* Return a string representation of this params types, like 'string' or | ||
* 'number | boolean' or '...number' | ||
* @returns {string} | ||
*/ | ||
Param.prototype.toString = function () { | ||
return (this.varArgs ? '...' : '') + this.types.join('|'); | ||
}; | ||
/** | ||
* A function signature | ||
* @param {string | Array.<string>} params Array with the type(s) of each parameter, | ||
* or a comma separated string with types | ||
* @param {function} fn The actual function | ||
* @param {string | string[]} params Array with the type(s) of each parameter, | ||
* or a comma separated string with types | ||
* @param {function} fn The actual function | ||
* @constructor | ||
@@ -97,28 +194,629 @@ */ | ||
if (typeof params === 'string') { | ||
this.params = (params !== '') ? params.split(',').map(function (param) { | ||
return param.trim(); | ||
this.params = (params !== '') ? params.split(',').map(function (types) { | ||
return new Param(types); | ||
}) : []; | ||
} | ||
else if (Array.isArray(params)) { | ||
this.params = params.map(function (types) { | ||
return new Param(types); | ||
}); | ||
} | ||
else { | ||
this.params = params; | ||
throw new Error('string or Array expected'); | ||
} | ||
// check variable arguments operator '...' | ||
var withVarArgs = this.params.filter(function (param) { | ||
return param.varArgs; | ||
}); | ||
if (withVarArgs.length === 0) { | ||
this.varArgs = false; | ||
} | ||
else if (withVarArgs[0] === this.params[this.params.length - 1]) { | ||
this.varArgs = true; | ||
} | ||
else { | ||
throw new SyntaxError('Unexpected variable arguments operator "..."'); | ||
} | ||
this.fn = fn; | ||
} | ||
// TODO: implement function Signature.split | ||
// TODO: implement function Signature.merge | ||
// TODO: implement function Signature.toString | ||
/** | ||
* Split params with multiple types in separate signatures, | ||
* for example split a Signature "string | number" into two signatures. | ||
* @return {Signature[]} Returns an array with signatures (at least one) | ||
*/ | ||
Signature.prototype.split = function () { | ||
var signatures = []; | ||
function _iterate(signature, types, index) { | ||
if (index < signature.params.length) { | ||
var param = signature.params[index]; | ||
if (index == signature.params.length - 1 && signature.varArgs) { | ||
// last parameter of a varArgs signature. Do not split the varArgs parameter | ||
_iterate(signature, types.concat(param), index + 1); | ||
} | ||
else { | ||
param.types.forEach(function (type) { | ||
_iterate(signature, types.concat(new Param(type, param.varArgs)), index + 1); | ||
}); | ||
} | ||
} | ||
else { | ||
signatures.push(new Signature(types, signature.fn)); | ||
} | ||
} | ||
_iterate(this, [], 0); | ||
return signatures; | ||
}; | ||
/** | ||
* split all raw signatures into an array with splitted params | ||
* A node is used to create a node tree to recursively traverse parameters | ||
* of a function. Nodes have either: | ||
* - Child nodes in a map `types`. | ||
* - No child nodes but a function `fn`, the function to be called for | ||
* This signature. | ||
* @param {Param} type The parameter type of this node | ||
* @constructor | ||
*/ | ||
function Node (type) { | ||
this.type = type; | ||
this.fn = null; | ||
this.varArgs = false; // true if variable args '...' | ||
this.childs = {}; | ||
} | ||
/** | ||
* Calculates the maximum depth (level) of nested childs | ||
* @return {number} Returns the maximum depth (zero if no childs, one if | ||
* it has childs without childs, etc) | ||
*/ | ||
Node.prototype.depth = function () { | ||
var level = 0; | ||
Object.keys(this.childs).forEach(function (type) { | ||
var childLevel = this.childs[type].depth() + 1; | ||
level = Math.max(level, childLevel); | ||
}.bind(this)); | ||
return level; | ||
}; | ||
/** | ||
* Test recursively whether this Node or any of it's childs need conversions | ||
*/ | ||
Node.prototype.hasConversions = function () { | ||
if (this._getConversions().length > 0) { | ||
return true; | ||
} | ||
if (this.type && this.type.varArgs && this._getVarArgConversions().length > 0) { | ||
return true; | ||
} | ||
if (this.childs) { | ||
for (var type in this.childs) { | ||
if (this.childs.hasOwnProperty(type)) { | ||
if (this.childs[type].hasConversions()) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
}; | ||
/** | ||
* Returns a string with JavaScript code for this function | ||
* | ||
* @param {{refs: Refs, args: string[], types: Param[], tests: string[], prefix: string, conversions: boolean, exceptions: boolean}} params | ||
* @param {{from: string, to: string, convert: function}} [conversion] | ||
* | ||
* Where: | ||
* {Refs} refs Object to store function references | ||
* {string[]} args Argument names, like ['arg0', 'arg1', ...], | ||
* but can also contain conversions like ['arg0', 'convert1(arg1)'] | ||
* {Param[]} types Array with parameter types parsed so far | ||
* {string[]} tests Type tests, like ['test0', 'test2', ...] | ||
* {string} prefix A number of spaces to prefix for every line of code | ||
* {boolean} conversions A boolean which is true when the generated | ||
* code must do type conversions | ||
* {boolean} exceptions A boolean which is true when the generated code | ||
* must throw exceptions when there is no signature match | ||
* | ||
* @returns {string} code | ||
* | ||
* @protected | ||
*/ | ||
Node.prototype._toCode = function (params, conversion) { | ||
var code = []; | ||
var prefix = params.prefix; | ||
var ref = this.fn ? params.refs.add(this.fn, 'signature') : undefined; | ||
var arg = this.varArgs ? 'varArgs' : ('arg' + params.args.length); | ||
var type = this.type; | ||
var test; | ||
var nextArg; | ||
var convert; | ||
if (conversion) { | ||
test = params.refs.add(getTypeTest(conversion.from), 'test'); | ||
convert = params.refs.add(conversion.convert, 'convert'); | ||
nextArg = (type.varArgs === false) ? (convert + '(' + arg + ')') : arg; | ||
} | ||
else { | ||
test = (type.anyType === false) ? params.refs.add(getTypeTest(type.types[0]), 'test') : ''; | ||
convert = null; | ||
nextArg = arg; | ||
} | ||
var comment = '// type: ' + (convert ? (conversion.from + ', convert to ' + type) : type); | ||
var args = params.args.concat(nextArg); | ||
var types = params.types.concat(type); | ||
var tests = params.tests.concat(test); | ||
var nextParams = merge(params, { | ||
args: args, | ||
types: types, | ||
tests: tests | ||
}); | ||
if (this.varArgs) { | ||
// varArgs cannot have childs, it's the last argument | ||
if (type.anyType) { | ||
if (ref) { | ||
code.push(prefix + 'if (arguments.length >= ' + args.length + ') {'); | ||
code.push(prefix + ' var varArgs = [];'); | ||
code.push(prefix + ' for (var i = ' + (args.length - 1) + '; i < arguments.length; i++) {'); | ||
code.push(prefix + ' varArgs.push(arguments[i]);'); | ||
code.push(prefix + ' }'); | ||
code.push(prefix + ' return ' + ref + '(' + args.join(', ') + '); // signature: ' + types.join(', ')); | ||
code.push(prefix + '}'); | ||
// TODO: throw Exception | ||
} | ||
} | ||
else { | ||
if (ref) { | ||
var varTests = type.types.map(function (type) { | ||
var test = params.refs.add(getTypeTest(type), 'test'); | ||
return test + '(arguments[i])'; | ||
}) | ||
.join(' || '); | ||
code.push(prefix + 'if (arguments.length >= ' + args.length + ') {'); | ||
code.push(prefix + ' var match = true;'); | ||
code.push(prefix + ' var varArgs = [];'); | ||
code.push(prefix + ' for (var i = ' + (args.length - 1) + '; i < arguments.length; i++) {'); | ||
code.push(prefix + ' if (' + varTests + ') { // type: ' + type.types.join(' or ')); | ||
code.push(prefix + ' varArgs.push(arguments[i]);'); | ||
// iterate over the type conversions | ||
if (params.conversions) { | ||
this._getVarArgConversions().forEach(function (conversion) { | ||
var test = params.refs.add(getTypeTest(conversion.from), 'test'); | ||
var convert = params.refs.add(conversion.convert, 'convert'); | ||
var comment = '// type: ' + conversion.from + ', convert to ' + conversion.to; | ||
code.push(prefix + ' }'); | ||
code.push(prefix + ' else if (' + test + '(arguments[i])) { ' + comment); | ||
code.push(prefix + ' varArgs.push(' + convert + '(arguments[i]));'); | ||
}.bind(this)); | ||
} | ||
code.push(prefix + ' } else {'); | ||
if (params.exceptions) { | ||
var err = params.refs.add(unexpectedType, 'err'); | ||
code.push(prefix + ' throw ' + err + '(\'' + this.type.types + '\', arguments[i], i); // Unexpected type'); | ||
} | ||
else { | ||
code.push(prefix + ' match = false;'); | ||
code.push(prefix + ' break;'); | ||
} | ||
code.push(prefix + ' }'); | ||
code.push(prefix + ' }'); | ||
code.push(prefix + ' if (match) {'); | ||
code.push(prefix + ' return ' + ref + '(' + args.join(', ') + '); // signature: ' + types.join(', ')); | ||
code.push(prefix + ' }'); | ||
code.push(prefix + '}'); | ||
} | ||
} | ||
} | ||
else { | ||
if (type.anyType) { | ||
// any type (ordered last) | ||
code.push(this._innerCode(nextParams)); | ||
} | ||
else { | ||
code.push(prefix + 'if (' + test + '(' + arg + ')) { ' + comment); | ||
code.push(this._innerCode(merge(nextParams, {prefix: prefix + ' '}))); | ||
code.push(prefix + '}'); | ||
} | ||
} | ||
return code.join('\n'); | ||
}; | ||
/** | ||
* Sub function of Node.prototype._toNode | ||
* | ||
* @param {{refs: Refs, args: string[], types: Param[], tests: string[], prefix: string, conversions: boolean, exceptions: boolean}} params | ||
* | ||
* Where: | ||
* {Refs} refs Object to store function references | ||
* {string[]} args Argument names, like ['arg0', 'arg1', ...], | ||
* but can also contain conversions like ['arg0', 'convert1(arg1)'] | ||
* Must include the arg of the current node. | ||
* {Param[]} types Array with parameter types parsed so far | ||
* Must include the type of the current node. | ||
* {string[]} tests Type tests, like ['test0', 'test2', ...] | ||
* Must include the test of the current node. | ||
* {string} prefix A number of spaces to prefix for every line of code | ||
* {boolean} conversions A boolean which is true when the generated | ||
* code must do type conversions | ||
* {boolean} exceptions A boolean which is true when the generated code | ||
* must throw exceptions when there is no signature match | ||
* | ||
* @returns {string} code | ||
* | ||
* @protected | ||
*/ | ||
Node.prototype._innerCode = function (params) { | ||
var code = []; | ||
var prefix = params.prefix; | ||
var ref = this.fn ? params.refs.add(this.fn, 'signature') : undefined; | ||
if (ref) { | ||
code.push(prefix + 'if (arguments.length === ' + params.args.length + ') {'); | ||
code.push(prefix + ' return ' + ref + '(' + params.args.join(', ') + '); // signature: ' + params.types.join(', ')); | ||
code.push(prefix + '}'); | ||
} | ||
// iterate over the childs | ||
this._getChilds().forEach(function (child) { | ||
code.push(child._toCode(merge(params))); | ||
}); | ||
// handle conversions | ||
if (params.conversions) { | ||
var conversionsCode = this._conversionsToCode(params); | ||
if (conversionsCode.length > 0) { | ||
code.push(conversionsCode); | ||
} | ||
} | ||
if (params.exceptions) { | ||
code.push(this._exceptions(params)); | ||
} | ||
return code.join('\n'); | ||
}; | ||
/** | ||
* Create an unsupported type error | ||
* @param {string} expected String with comma separated types | ||
* @param {*} actual The actual argument | ||
* @param {number} index Index of the argument | ||
* @returns {TypeError} Returns a TypeError | ||
*/ | ||
function unexpectedType (expected, actual, index) { | ||
var arr = expected.split(','); | ||
var message = 'Unexpected type of argument'; | ||
var actualType = getTypeOf(actual); | ||
var err = new TypeError(message + '. Expected: ' + arr.join(' or ') + ', actual: ' + actualType + ', index: ' + index + '.'); | ||
err.data = { | ||
message: message, | ||
expected: arr, | ||
actual: actual, | ||
index: index | ||
}; | ||
return err; | ||
} | ||
/** | ||
* Create a too-few-arguments error | ||
* @param {string} expected String with comma separated types | ||
* @param {number} index index of the argument | ||
* @returns {TypeError} Returns a TypeError | ||
*/ | ||
function tooFewArguments(expected, index) { | ||
var arr = expected.split(','); | ||
var message = 'Too few arguments'; | ||
var err = new TypeError(message + '. Expected: ' + arr.join(' or ') + ', index: ' + index + '.'); | ||
err.data = { | ||
message: message, | ||
expected: arr, | ||
index: index | ||
}; | ||
return err; | ||
} | ||
/** | ||
* Create an too-many-arguments error | ||
* @param {number} expected The expected number of arguments | ||
* @param {number} actual The actual number of arguments | ||
* @returns {TypeError}Returns a TypeError | ||
*/ | ||
function tooManyArguments(expected, actual) { | ||
var message = 'Too many arguments'; | ||
var err = new TypeError(message + '. Expected: ' + expected + ', actual: ' + actual + '.'); | ||
err.data = { | ||
message: message, | ||
expected: expected, | ||
actual: actual | ||
}; | ||
return err; | ||
} | ||
/** | ||
* Create code to throw an error | ||
* | ||
* @param {{refs: Refs, args: string[], types: Param[], tests: string[], prefix: string, conversions: boolean, exceptions: boolean}} params | ||
* | ||
* Where: | ||
* {Refs} refs Object to store function references | ||
* {string[]} args Argument names, like ['arg0', 'arg1', ...], | ||
* but can also contain conversions like ['arg0', 'convert1(arg1)'] | ||
* Must include the arg of the current node. | ||
* {Param[]} types Array with parameter types parsed so far | ||
* Must include the type of the current node. | ||
* {string[]} tests Type tests, like ['test0', 'test2', ...] | ||
* Must include the test of the current node. | ||
* {string} prefix A number of spaces to prefix for every line of code | ||
* {boolean} conversions A boolean which is true when the generated | ||
* code must do type conversions | ||
* {boolean} exceptions A boolean which is true when the generated code | ||
* must throw exceptions when there is no signature match | ||
* | ||
* @returns {string} Code throwing an error | ||
* | ||
* @private | ||
*/ | ||
Node.prototype._exceptions = function (params) { | ||
var code = []; | ||
var prefix = params.prefix; | ||
var argCount = params.args.length; | ||
var arg = 'arg' + params.args.length; | ||
var err; | ||
var types = Object.keys(this.childs); | ||
var firstChild = types.length > 0 ? this.childs[types[0]] : undefined; | ||
if (firstChild && firstChild.varArgs) { | ||
err = params.refs.add(tooFewArguments, 'err'); | ||
code.push(prefix + 'throw ' + err + '(\'' + firstChild.type.types.join(',') + '\', arguments.length); // Too few arguments'); | ||
} | ||
else { | ||
if (types.length === 0) { | ||
code.push(prefix + 'if (arguments.length > ' + argCount + ') {'); | ||
err = params.refs.add(tooManyArguments, 'err'); | ||
code.push(prefix + ' throw ' + err + '(' + argCount + ', arguments.length); // Too many arguments'); | ||
code.push(prefix + '}'); | ||
} | ||
else { | ||
if (types.indexOf('any') === -1) { | ||
var errA = params.refs.add(tooFewArguments, 'err'); | ||
code.push(prefix + 'if (arguments.length === ' + argCount + ') {'); | ||
code.push(prefix + ' throw ' + errA + '(\'' + types.join(',') + '\', arguments.length); // Too few arguments'); | ||
code.push(prefix + '}'); | ||
var errB = params.refs.add(unexpectedType, 'err'); | ||
code.push(prefix + 'throw ' + errB + '(\'' + types.join(',') + '\', ' + arg + ', ' + argCount + '); // Unexpected type'); | ||
// TODO: add "Actual: ..." to the error message | ||
} | ||
} | ||
} | ||
return code.join('\n'); | ||
}; | ||
/** | ||
* Create a code representation for iterating over conversions | ||
* | ||
* @param {{refs: Refs, args: string[], types: Param[], tests: string[], prefix: string, conversions: boolean, exceptions: boolean}} params | ||
* | ||
* Where: | ||
* {Refs} refs Object to store function references | ||
* {string[]} args Argument names, like ['arg0', 'arg1', ...], | ||
* but can also contain conversions like ['arg0', 'convert1(arg1)'] | ||
* {Param[]} types Array with parameter types parsed so far | ||
* {string[]} tests Type tests, like ['test0', 'test2', ...] | ||
* {string} prefix A number of spaces to prefix for every line of code | ||
* {boolean} conversions A boolean which is true when the generated | ||
* code must do type conversions | ||
* {boolean} exceptions A boolean which is true when the generated code | ||
* must throw exceptions when there is no signature match | ||
* | ||
* @returns {string} code | ||
* | ||
* @protected | ||
*/ | ||
Node.prototype._conversionsToCode = function (params) { | ||
var code = []; | ||
// iterate over the type conversions | ||
this._getConversions().forEach(function (conversion) { | ||
var type = conversion.to; | ||
var child = this.childs[type]; | ||
code.push(child._toCode(params, conversion)); | ||
}.bind(this)); | ||
return code.join('\n'); | ||
}; | ||
/** | ||
* Get the childs of this node ordered by type | ||
* @return {Node[]} Returns an array with Nodes | ||
* @protected | ||
*/ | ||
Node.prototype._getChilds = function () { | ||
return Object.keys(this.childs) | ||
.sort(compareTypes) | ||
.map(function (type) { | ||
return this.childs[type]; | ||
}.bind(this)); | ||
}; | ||
/** | ||
* Get the conversions relevant for this Node | ||
* @returns {Array} Array with conversion objects | ||
* @protected | ||
*/ | ||
Node.prototype._getConversions = function () { | ||
// if there is a varArgs child, there is no need to do conversions separately, | ||
// that is handled by the varArg loop | ||
var hasVarArgs = this._getChilds().some(function (child) { | ||
return child.type.varArgs; | ||
}); | ||
if (hasVarArgs) { | ||
return []; | ||
} | ||
// filter the relevant type conversions | ||
var handled = {}; | ||
return typed.conversions | ||
.filter(function (conversion) { | ||
if (this.childs[conversion.from] === undefined && | ||
this.childs[conversion.to] !== undefined && | ||
!handled[conversion.from]) { | ||
handled[conversion.from] = true; | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
}.bind(this)); | ||
}; | ||
/** | ||
* Get the conversions relevant for a Node with variable arguments | ||
* @returns {Array} Array with conversion objects | ||
* @protected | ||
*/ | ||
Node.prototype._getVarArgConversions = function () { | ||
// filter the relevant type conversions | ||
var handled = {}; | ||
return typed.conversions | ||
.filter(function (conversion) { | ||
if (this.type.types.indexOf(conversion.from) === -1 && | ||
this.type.types.indexOf(conversion.to) !== -1 && | ||
!handled[conversion.from]) { | ||
handled[conversion.from] = true; | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
}.bind(this)); | ||
}; | ||
/** | ||
* The root node of a node tree | ||
* @param {string} [name] Optional function name | ||
* @constructor | ||
*/ | ||
function RootNode(name) { | ||
this.name = name || ''; | ||
this.fn = null; | ||
this.childs = {}; | ||
} | ||
RootNode.prototype = Object.create(Node.prototype); | ||
/** | ||
* Returns a string with JavaScript code for this function | ||
* @param {Refs} refs Object to store function references | ||
* @return {string} code | ||
*/ | ||
RootNode.prototype.toCode = function (refs) { | ||
var code = []; | ||
// create an array with all argument names | ||
var argCount = this.depth(); | ||
var args = []; | ||
for (var i = 0; i < argCount; i++) { | ||
args[i] = 'arg' + i; | ||
} | ||
// check whether the function at hand needs conversions | ||
var hasConversions = this.hasConversions(); | ||
// function begin | ||
code.push('return function ' + this.name + '(' + args.join(', ') + ') {'); | ||
// function signature with zero arguments | ||
var ref = this.fn ? refs.add(this.fn, 'signature') : undefined; | ||
if (ref) { | ||
code.push(' if (arguments.length === 0) {'); | ||
code.push(' return ' + ref + '(); // signature: (empty)'); | ||
code.push(' }'); | ||
} | ||
var params = { | ||
refs: refs, | ||
args: [], | ||
types: [], | ||
tests: [], | ||
prefix: ' ' | ||
}; | ||
if (hasConversions) { | ||
// create two recursive trees: the first checks exact matches, the second | ||
// checks all possible conversions. This must be done in two separate | ||
// loops, else you can get unnecessary conversions. | ||
// exact matches | ||
// iterate over the childs | ||
code.push(' // exactly matching signatures'); | ||
this._getChilds().forEach(function (child) { | ||
code.push(child._toCode(merge(params, { | ||
conversions: false, | ||
exceptions: false | ||
}))); | ||
}); | ||
// matches and conversions | ||
code.push(' // convert into matching signatures'); | ||
this._getChilds().forEach(function (child) { | ||
code.push(child._toCode(merge(params, { | ||
conversions: true, | ||
exceptions: true | ||
}))); | ||
}); | ||
code.push(this._conversionsToCode(merge(params, { | ||
conversions: true, | ||
exceptions: true | ||
}))); | ||
} | ||
else { | ||
// no conversions needed for this function. Create one recursive tree to test exact matches | ||
code.push(' // exactly matching signatures'); | ||
this._getChilds().forEach(function (child) { | ||
code.push(child._toCode(merge(params, { | ||
conversions: false, | ||
exceptions: true | ||
}))); | ||
}); | ||
} | ||
// function end | ||
code.push(this._exceptions(params)); | ||
code.push('}'); | ||
return code.join('\n'); | ||
}; | ||
/** | ||
* Split all raw signatures into an array with (split) Signatures | ||
* @param {Object.<string, function>} rawSignatures | ||
* @return {Array.<{params: Array.<string>, fn: function}>} Returns splitted signatures | ||
* @return {Signature[]} Returns an array with split signatures | ||
*/ | ||
function splitSignatures(rawSignatures) { | ||
return Object.keys(rawSignatures).map(function (params) { | ||
function parseSignatures(rawSignatures) { | ||
return Object.keys(rawSignatures).reduce(function (signatures, params) { | ||
var fn = rawSignatures[params]; | ||
return new Signature(params, fn); | ||
}); | ||
var signature = new Signature(params, fn); | ||
// TODO: split params containing an '|' into multiple entries | ||
return signatures.concat(signature.split()); | ||
}, []); | ||
} | ||
@@ -128,4 +826,4 @@ | ||
* create a map with normalized signatures as key and the function as value | ||
* @param {Array.<{params: Array.<string>, fn: function}>} signatures An array with splitted signatures | ||
* @return {{}} Returns a map with normalized signatures | ||
* @param {Signature[]} signatures An array with split signatures | ||
* @return {Object} Returns a map with normalized signatures | ||
*/ | ||
@@ -143,182 +841,112 @@ function normalizeSignatures(signatures) { | ||
return normalized | ||
return normalized; | ||
} | ||
/** | ||
* create an array with for every parameter an array with possible types | ||
* @param {Array.<{params: Array.<string>, fn: function}>} signatures An array with splitted signatures | ||
* @return {Array.<Array.<string>>} Returns an array with allowed types per parameter | ||
* Parse an object with signatures. Creates a recursive node tree for | ||
* traversing the number and type of parameters. | ||
* @param {string} [name] Function name. Optional | ||
* @param {Signature[]} signatures An array with split signatures | ||
* @return {RootNode} Returns a node tree | ||
*/ | ||
function splitTypes(signatures) { | ||
var types = []; | ||
function parseTree(name, signatures) { | ||
var root = new RootNode(name); | ||
signatures.forEach(function (entry) { | ||
entry.params.forEach(function (param, i) { | ||
if (!types[i]) { | ||
types[i] = []; | ||
} | ||
if (types[i].indexOf(param) == -1) { | ||
types[i].push(param); | ||
} | ||
}); | ||
}); | ||
signatures.forEach(function (signature) { | ||
var params = signature.params.concat([]); | ||
return types; | ||
} | ||
/** | ||
* create a recursive tree for traversing the number and type of parameters | ||
* @param {Array.<{params: Array.<string>, fn: function}>} signatures An array with splitted signatures | ||
* @returns {{}} | ||
*/ | ||
function createParamsTree(signatures) { | ||
var tree = {}; | ||
signatures.forEach(function (entry) { | ||
var params = entry.params.concat([]); | ||
// get the tree entry for the current number of arguments | ||
var obj = tree[params.length]; | ||
if (!obj) { | ||
obj = tree[params.length] = { | ||
signature: [], | ||
fn: null, | ||
types: {} | ||
}; | ||
} | ||
// loop over all parameters, create a nested structure | ||
var node = root; | ||
while(params.length > 0) { | ||
var param = params.shift(); | ||
if (!obj.types[param]) { | ||
obj.types[param] = { | ||
signature: obj.signature.concat(param), | ||
fn: null, | ||
types: {} | ||
}; | ||
var type = param.toString(); | ||
var child = node.childs[type]; | ||
if (child === undefined) { | ||
child = node.childs[type] = new Node(param); | ||
} | ||
obj = obj.types[param]; | ||
node = child; | ||
} | ||
// add the function as leaf | ||
obj.fn = entry.fn; | ||
// add the function as leaf of the innermost node | ||
node.fn = signature.fn; | ||
node.varArgs = signature.varArgs; | ||
}); | ||
return tree; | ||
return root; | ||
} | ||
/** | ||
* Compose a function from sub-functions each handling a single type signature. | ||
* @param {string} [name] An optional name for the function | ||
* @param {Object.<string, function>} signatures | ||
* A map with the type signature as key and the sub-function as value | ||
* @return {function} Returns the composed function | ||
* Minify JavaScript code of a typed function | ||
* @param {string} code | ||
* @return {string} Returns (roughly) minified code | ||
*/ | ||
function typed(name, signatures) { | ||
// handle arguments | ||
if (!signatures) { | ||
signatures = name; | ||
name = null; | ||
} | ||
function minify (code) { | ||
return code | ||
.replace(/\/\/.*/g, '') // remove comments | ||
.replace(/\s*\n\s*/gm, '') // remove spaces and returns | ||
var defs = new Defs(); | ||
var structure = splitSignatures(signatures); | ||
// remove whitespaces | ||
.replace(/\s?([{}()=<>;,]+)\s?/g, function (match, delimiter) { | ||
return delimiter; | ||
}) | ||
function switchTypes(signature, args, prefix) { | ||
var code = []; | ||
// replace long variable names like 'signature1' with their first letter 's1' | ||
.replace(/(signature|test|convert|arg)(?=\d)|varArgs|match/g, function (v) { | ||
// NOTE: all matched variable names must have a unique first letter!! | ||
return v.charAt(0); | ||
}); | ||
} | ||
if (signature.fn !== null) { | ||
var def = defs.add(signature.fn, 'signature'); | ||
code.push(prefix + 'return ' + def + '(' + args.join(', ') +'); // signature: ' + signature.signature); | ||
} | ||
else { | ||
// add entries for the provided types | ||
Object.keys(signature.types) | ||
.sort(compareTypes) | ||
.forEach(function (type, index) { | ||
var arg = 'arg' + args.length; | ||
/** | ||
* Compose a function from sub-functions each handling a single type signature. | ||
* Signatures: | ||
* typed(signature: string, fn: function) | ||
* typed(name: string, signature: string, fn: function) | ||
* typed(signatures: Object.<string, function>) | ||
* typed(name: string, signatures: Object.<string, function>) | ||
* | ||
* @param {string | null} name | ||
* @param {Object.<string, function>} signatures | ||
* @return {function} Returns the typed function | ||
* @private | ||
*/ | ||
function _typed(name, signatures) { | ||
var refs = new Refs(); | ||
var before; | ||
var after; | ||
var nextPrefix = prefix + ' '; | ||
if (type == '*') { | ||
before = (index > 0 ? 'else {' : ''); | ||
after = (index > 0 ? '}' : ''); | ||
if (index == 0) {nextPrefix = prefix;} | ||
} | ||
else { | ||
var def = defs.add(getTest(type), 'test') + '(' + arg + ')'; | ||
before = 'if (' + def + ') { // type: ' + type; | ||
after = '}'; | ||
} | ||
// parse signatures, create a node tree | ||
var _signatures = parseSignatures(signatures); | ||
var tree = parseTree(name, _signatures); | ||
if (before) code.push(prefix + before); | ||
code = code.concat(switchTypes(signature.types[type], args.concat(arg), nextPrefix)); | ||
if (after) code.push(prefix + after); | ||
}); | ||
if (_signatures.length == 0) { | ||
throw new Error('No signatures provided'); | ||
} | ||
// add entries for type conversions | ||
var added = {}; | ||
typed.conversions | ||
.filter(function (conversion) { | ||
return signature.types[conversion.to] && | ||
!signature.types[conversion.from]; | ||
}) | ||
.forEach(function (conversion) { | ||
if (!added[conversion.from]) { | ||
added[conversion.from] = true; | ||
//console.log('TREE\n' + JSON.stringify(tree, null, 2)); // TODO: cleanup | ||
//console.log('TREE', tree); // TODO: cleanup | ||
var arg = 'arg' + args.length; | ||
var test = defs.add(getTest(conversion.from), 'test') + '(' + arg + ')'; | ||
var convert = defs.add(conversion.convert, 'convert') + '(' + arg + ')'; | ||
var treeCode = tree.toCode(refs); | ||
var refsCode = refs.toCode(); | ||
code.push(prefix + 'if (' + test + ') { // type: ' + conversion.from + ', convert to ' + conversion.to); | ||
code = code.concat(switchTypes(signature.types[conversion.to], args.concat(convert), prefix + ' ')); | ||
code.push(prefix + '}'); | ||
} | ||
}); | ||
} | ||
// generate JavaScript code | ||
var factory = [ | ||
'(function (' + refs.name + ') {', | ||
refsCode, | ||
treeCode, | ||
'})' | ||
].join('\n'); | ||
return code; | ||
if (typed.config.minify) { | ||
factory = minify(factory); | ||
} | ||
var types = splitTypes(structure); | ||
var params = []; | ||
for (var i = 0; i < types.length; i++) { // we can't use .map here, some entries may be undefined | ||
params.push('arg' + i); | ||
} | ||
//console.log('CODE\n' + treeCode); // TODO: cleanup | ||
var code = []; | ||
var tree = createParamsTree(structure); | ||
var paramCounts = Object.keys(tree); | ||
code.push('return function ' + (name || '') + '(' + params.join(', ') + ') {'); | ||
paramCounts | ||
.sort(compareNumbers) | ||
.forEach(function (count, index) { | ||
var signature = tree[count]; | ||
var args = []; | ||
var statement = (index == 0) ? 'if' : 'else if'; | ||
code.push(' ' + statement + ' (arguments.length == ' + count + ') {'); | ||
code = code.concat(switchTypes(signature, args, ' ')); | ||
// evaluate the JavaScript code and attach function references | ||
var fn = eval(factory)(refs); | ||
code.push(' }'); | ||
if (index == paramCounts.length - 1) { | ||
code.push(' else {'); | ||
code.push(' throw new TypeError(\'Wrong number of arguments\');'); // TODO: output the allowed numbers | ||
code.push(' }'); | ||
} | ||
}); | ||
code.push(' throw new TypeError(\'Wrong function signature\');'); // TODO: output the actual signature | ||
code.push('}'); | ||
//console.log('FN\n' + fn.toString()); // TODO: cleanup | ||
var factory = []; | ||
factory.push('(function (defs) {'); | ||
factory = factory.concat(defs.code('defs')); | ||
factory = factory.concat(code); | ||
factory.push('})'); | ||
var fn = eval(factory.join('\n'))(defs); | ||
// attach the signatures with sub-functions to the constructed function | ||
fn.signatures = normalizeSignatures(structure); // normalized signatures | ||
fn.signatures = normalizeSignatures(_signatures); // normalized signatures | ||
@@ -329,4 +957,5 @@ return fn; | ||
// data type tests | ||
typed.types = { | ||
var types = { | ||
'null': function (x) {return x === null}, | ||
'undefined':function (x) {return x === undefined}, | ||
'boolean': function (x) {return typeof x === 'boolean'}, | ||
@@ -342,24 +971,67 @@ 'number': function (x) {return typeof x === 'number'}, | ||
function getTest(type) { | ||
var test = typed.types[type]; | ||
if (!test) { | ||
var matches = Object.keys(typed.types) | ||
.filter(function (t) { | ||
return t.toLowerCase() == type.toLowerCase(); | ||
}) | ||
.map(function (t) { | ||
return '"' + t + '"'; | ||
}); | ||
throw new Error('Unknown type "' + type + '"' + | ||
(matches.length ? ('. Did you mean ' + matches.join(', or ') + '?') : '')); | ||
/** | ||
* Get the type of a value | ||
* @param {*} x | ||
* @returns {string} Returns a string with the type of value | ||
*/ | ||
function getTypeOf(x) { | ||
for (var type in types) { | ||
if (types.hasOwnProperty(type)) { | ||
if (types[type](x)) return type; | ||
} | ||
} | ||
return test; | ||
return 'unknown'; | ||
} | ||
// type conversions | ||
// order is important | ||
typed.conversions = []; | ||
// configuration | ||
var config = { | ||
minify: true | ||
}; | ||
// type conversions. Order is important | ||
var conversions = []; | ||
// temporary object for holding types and conversions, for constructing | ||
// the `typed` function itself | ||
// TODO: find a more elegant solution for this | ||
var typed = { | ||
config: config, | ||
types: types, | ||
conversions: conversions | ||
}; | ||
/** | ||
* Construct the typed function itself with various signatures | ||
* | ||
* Signatures: | ||
* | ||
* typed(signature: string, fn: function) | ||
* typed(name: string, signature: string, fn: function) | ||
* typed(signatures: Object.<string, function>) | ||
* typed(name: string, signatures: Object.<string, function>) | ||
*/ | ||
typed = _typed('typed', { | ||
'Object': function (signatures) { | ||
return _typed(null, signatures); | ||
}, | ||
'string, Object': _typed, | ||
'string, function': function (signature, fn) { | ||
var signatures = {}; | ||
signatures[signature] = fn; | ||
return _typed(null, signatures); | ||
}, | ||
'string, string, function': function(name, signature, fn) { | ||
var signatures = {}; | ||
signatures[signature] = fn; | ||
return _typed(name, signatures); | ||
} | ||
}); | ||
// attach types and conversions to the final `typed` function | ||
typed.config = config; | ||
typed.types = types; | ||
typed.conversions = conversions; | ||
return typed; | ||
})); | ||
@@ -1,1 +0,1 @@ | ||
!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.typed=e()}(this,function(){"use strict";function compareTypes(t,e){return"*"===t?1:"*"===e?-1:"Object"===t?1:"Object"===e?-1:0}function compareNumbers(t,e){return t>e}function Defs(){}function Signature(t,e){this.params="string"==typeof t?""!==t?t.split(",").map(function(t){return t.trim()}):[]:t,this.fn=e}function splitSignatures(t){return Object.keys(t).map(function(e){var n=t[e];return new Signature(e,n)})}function normalizeSignatures(t){var e={};return t.map(function(t){var n=t.params.join(",");if(n in e)throw new Error('Error: signature "'+n+'" defined twice');e[n]=t.fn}),e}function splitTypes(t){var e=[];return t.forEach(function(t){t.params.forEach(function(t,n){e[n]||(e[n]=[]),-1==e[n].indexOf(t)&&e[n].push(t)})}),e}function createParamsTree(t){var e={};return t.forEach(function(t){var n=t.params.concat([]),r=e[n.length];for(r||(r=e[n.length]={signature:[],fn:null,types:{}});n.length>0;){var o=n.shift();r.types[o]||(r.types[o]={signature:r.signature.concat(o),fn:null,types:{}}),r=r.types[o]}r.fn=t.fn}),e}function typed(name,signatures){function switchTypes(t,e,n){var r=[];if(null!==t.fn){var o=defs.add(t.fn,"signature");r.push(n+"return "+o+"("+e.join(", ")+"); // signature: "+t.signature)}else{Object.keys(t.types).sort(compareTypes).forEach(function(o,s){var u,a,c="arg"+e.length,i=n+" ";if("*"==o)u=s>0?"else {":"",a=s>0?"}":"",0==s&&(i=n);else{var f=defs.add(getTest(o),"test")+"("+c+")";u="if ("+f+") { // type: "+o,a="}"}u&&r.push(n+u),r=r.concat(switchTypes(t.types[o],e.concat(c),i)),a&&r.push(n+a)});var s={};typed.conversions.filter(function(e){return t.types[e.to]&&!t.types[e.from]}).forEach(function(o){if(!s[o.from]){s[o.from]=!0;var u="arg"+e.length,a=defs.add(getTest(o.from),"test")+"("+u+")",c=defs.add(o.convert,"convert")+"("+u+")";r.push(n+"if ("+a+") { // type: "+o.from+", convert to "+o.to),r=r.concat(switchTypes(t.types[o.to],e.concat(c),n+" ")),r.push(n+"}")}})}return r}signatures||(signatures=name,name=null);for(var defs=new Defs,structure=splitSignatures(signatures),types=splitTypes(structure),params=[],i=0;i<types.length;i++)params.push("arg"+i);var code=[],tree=createParamsTree(structure),paramCounts=Object.keys(tree);code.push("return function "+(name||"")+"("+params.join(", ")+") {"),paramCounts.sort(compareNumbers).forEach(function(t,e){var n=tree[t],r=[],o=0==e?"if":"else if";code.push(" "+o+" (arguments.length == "+t+") {"),code=code.concat(switchTypes(n,r," ")),code.push(" }"),e==paramCounts.length-1&&(code.push(" else {"),code.push(" throw new TypeError('Wrong number of arguments');"),code.push(" }"))}),code.push(" throw new TypeError('Wrong function signature');"),code.push("}");var factory=[];factory.push("(function (defs) {"),factory=factory.concat(defs.code("defs")),factory=factory.concat(code),factory.push("})");var fn=eval(factory.join("\n"))(defs);return fn.signatures=normalizeSignatures(structure),fn}function getTest(t){var e=typed.types[t];if(!e){var n=Object.keys(typed.types).filter(function(e){return e.toLowerCase()==t.toLowerCase()}).map(function(t){return'"'+t+'"'});throw new Error('Unknown type "'+t+'"'+(n.length?". Did you mean "+n.join(", or ")+"?":""))}return e}return Defs.prototype.add=function(t,e){e=e||"fn",this[e]||(this[e]=[]);var n=this[e].indexOf(t);return-1==n&&(n=this[e].length,this[e].push(t)),e+n},Defs.prototype.code=function(t){var e=this,n=[];return t=t||"defs",Object.keys(this).forEach(function(r){var o=e[r];o.forEach(function(e,o){n.push("var "+r+o+" = "+t+"['"+r+"']["+o+"];")})}),n},typed.types={"null":function(t){return null===t},"boolean":function(t){return"boolean"==typeof t},number:function(t){return"number"==typeof t},string:function(t){return"string"==typeof t},"function":function(t){return"function"==typeof t},Array:function(t){return Array.isArray(t)},Date:function(t){return t instanceof Date},RegExp:function(t){return t instanceof RegExp},Object:function(t){return"object"==typeof t}},typed.conversions=[],typed}); | ||
!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.typed=t()}(this,function(){"use strict";function compareTypes(e,t){return"any"===e?1:"any"===t?-1:"Object"===e?1:"Object"===t?-1:0}function merge(){for(var e={},t=0;t<arguments.length;t++){var r=arguments[t];for(var n in r)r.hasOwnProperty(n)&&(e[n]=r[n])}return e}function getTypeTest(e){var t=typed.types[e];if(!t){var r=Object.keys(typed.types).filter(function(t){return t.toLowerCase()==e.toLowerCase()}).map(function(e){return'"'+e+'"'});throw new Error('Unknown type "'+e+'"'+(r.length?". Did you mean "+r.join(", or ")+"?":""))}return t}function Refs(e){this.name=e||"refs",this.categories={}}function Param(e,t){if("string"==typeof e)this.types=e.split("|").map(function(e){return e.trim()});else{if(!Array.isArray(e)){if(e instanceof Param)return e.clone();throw new Error("String or Array expected")}this.types=e}void 0!==this.types[0]&&"..."==this.types[0].substring(0,3)?(this.types[0]=this.types[0].substring(3)||"any",this.varArgs=!0):this.varArgs=t||!1,this.anyType=this.types.some(function(e){return"any"==e})}function Signature(e,t){if("string"==typeof e)this.params=""!==e?e.split(",").map(function(e){return new Param(e)}):[];else{if(!Array.isArray(e))throw new Error("string or Array expected");this.params=e.map(function(e){return new Param(e)})}var r=this.params.filter(function(e){return e.varArgs});if(0===r.length)this.varArgs=!1;else{if(r[0]!==this.params[this.params.length-1])throw new SyntaxError('Unexpected variable arguments operator "..."');this.varArgs=!0}this.fn=t}function Node(e){this.type=e,this.fn=null,this.varArgs=!1,this.childs={}}function unexpectedType(e,t,r){var n=e.split(","),s="Unexpected type of argument",i=getTypeOf(t),o=new TypeError(s+". Expected: "+n.join(" or ")+", actual: "+i+", index: "+r+".");return o.data={message:s,expected:n,actual:t,index:r},o}function tooFewArguments(e,t){var r=e.split(","),n="Too few arguments",s=new TypeError(n+". Expected: "+r.join(" or ")+", index: "+t+".");return s.data={message:n,expected:r,index:t},s}function tooManyArguments(e,t){var r="Too many arguments",n=new TypeError(r+". Expected: "+e+", actual: "+t+".");return n.data={message:r,expected:e,actual:t},n}function RootNode(e){this.name=e||"",this.fn=null,this.childs={}}function parseSignatures(e){return Object.keys(e).reduce(function(t,r){var n=e[r],s=new Signature(r,n);return t.concat(s.split())},[])}function normalizeSignatures(e){var t={};return e.map(function(e){var r=e.params.join(",");if(r in t)throw new Error('Error: signature "'+r+'" defined twice');t[r]=e.fn}),t}function parseTree(e,t){var r=new RootNode(e);return t.forEach(function(e){for(var t=e.params.concat([]),n=r;t.length>0;){var s=t.shift(),i=s.toString(),o=n.childs[i];void 0===o&&(o=n.childs[i]=new Node(s)),n=o}n.fn=e.fn,n.varArgs=e.varArgs}),r}function minify(e){return e.replace(/\/\/.*/g,"").replace(/\s*\n\s*/gm,"").replace(/\s?([{}()=<>;,]+)\s?/g,function(e,t){return t}).replace(/(signature|test|convert|arg)(?=\d)|varArgs|match/g,function(e){return e.charAt(0)})}function _typed(name,signatures){var refs=new Refs,_signatures=parseSignatures(signatures),tree=parseTree(name,_signatures);if(0==_signatures.length)throw new Error("No signatures provided");var treeCode=tree.toCode(refs),refsCode=refs.toCode(),factory=["(function ("+refs.name+") {",refsCode,treeCode,"})"].join("\n");typed.config.minify&&(factory=minify(factory));var fn=eval(factory)(refs);return fn.signatures=normalizeSignatures(_signatures),fn}function getTypeOf(e){for(var t in types)if(types.hasOwnProperty(t)&&types[t](e))return t;return"unknown"}Refs.prototype.add=function(e,t){var r=t||"fn";this.categories[r]||(this.categories[r]=[]);var n=this.categories[r].indexOf(e);return-1==n&&(n=this.categories[r].length,this.categories[r].push(e)),r+n},Refs.prototype.toCode=function(){var e=[],t=this.name+".categories",r=this.categories;return Object.keys(r).forEach(function(n){r[n].forEach(function(r,s){e.push("var "+n+s+" = "+t+"['"+n+"']["+s+"];")})}),e.join("\n")},Param.prototype.clone=function(){return new Param(this.types.slice(),this.varArgs)},Param.prototype.toString=function(){return(this.varArgs?"...":"")+this.types.join("|")},Signature.prototype.split=function(){function e(r,n,s){if(s<r.params.length){var i=r.params[s];s==r.params.length-1&&r.varArgs?e(r,n.concat(i),s+1):i.types.forEach(function(t){e(r,n.concat(new Param(t,i.varArgs)),s+1)})}else t.push(new Signature(n,r.fn))}var t=[];return e(this,[],0),t},Node.prototype.depth=function(){var e=0;return Object.keys(this.childs).forEach(function(t){var r=this.childs[t].depth()+1;e=Math.max(e,r)}.bind(this)),e},Node.prototype.hasConversions=function(){if(this._getConversions().length>0)return!0;if(this.type&&this.type.varArgs&&this._getVarArgConversions().length>0)return!0;if(this.childs)for(var e in this.childs)if(this.childs.hasOwnProperty(e)&&this.childs[e].hasConversions())return!0;return!1},Node.prototype._toCode=function(e,t){var r,n,s,i=[],o=e.prefix,a=this.fn?e.refs.add(this.fn,"signature"):void 0,u=this.varArgs?"varArgs":"arg"+e.args.length,p=this.type;t?(r=e.refs.add(getTypeTest(t.from),"test"),s=e.refs.add(t.convert,"convert"),n=p.varArgs===!1?s+"("+u+")":u):(r=p.anyType===!1?e.refs.add(getTypeTest(p.types[0]),"test"):"",s=null,n=u);var h="// type: "+(s?t.from+", convert to "+p:p),f=e.args.concat(n),c=e.types.concat(p),g=e.tests.concat(r),d=merge(e,{args:f,types:c,tests:g});if(this.varArgs){if(p.anyType)a&&(i.push(o+"if (arguments.length >= "+f.length+") {"),i.push(o+" var varArgs = [];"),i.push(o+" for (var i = "+(f.length-1)+"; i < arguments.length; i++) {"),i.push(o+" varArgs.push(arguments[i]);"),i.push(o+" }"),i.push(o+" return "+a+"("+f.join(", ")+"); // signature: "+c.join(", ")),i.push(o+"}"));else if(a){var y=p.types.map(function(t){var r=e.refs.add(getTypeTest(t),"test");return r+"(arguments[i])"}).join(" || ");if(i.push(o+"if (arguments.length >= "+f.length+") {"),i.push(o+" var match = true;"),i.push(o+" var varArgs = [];"),i.push(o+" for (var i = "+(f.length-1)+"; i < arguments.length; i++) {"),i.push(o+" if ("+y+") { // type: "+p.types.join(" or ")),i.push(o+" varArgs.push(arguments[i]);"),e.conversions&&this._getVarArgConversions().forEach(function(t){var r=e.refs.add(getTypeTest(t.from),"test"),n=e.refs.add(t.convert,"convert"),s="// type: "+t.from+", convert to "+t.to;i.push(o+" }"),i.push(o+" else if ("+r+"(arguments[i])) { "+s),i.push(o+" varArgs.push("+n+"(arguments[i]));")}.bind(this)),i.push(o+" } else {"),e.exceptions){var v=e.refs.add(unexpectedType,"err");i.push(o+" throw "+v+"('"+this.type.types+"', arguments[i], i); // Unexpected type")}else i.push(o+" match = false;"),i.push(o+" break;");i.push(o+" }"),i.push(o+" }"),i.push(o+" if (match) {"),i.push(o+" return "+a+"("+f.join(", ")+"); // signature: "+c.join(", ")),i.push(o+" }"),i.push(o+"}")}}else p.anyType?i.push(this._innerCode(d)):(i.push(o+"if ("+r+"("+u+")) { "+h),i.push(this._innerCode(merge(d,{prefix:o+" "}))),i.push(o+"}"));return i.join("\n")},Node.prototype._innerCode=function(e){var t=[],r=e.prefix,n=this.fn?e.refs.add(this.fn,"signature"):void 0;if(n&&(t.push(r+"if (arguments.length === "+e.args.length+") {"),t.push(r+" return "+n+"("+e.args.join(", ")+"); // signature: "+e.types.join(", ")),t.push(r+"}")),this._getChilds().forEach(function(r){t.push(r._toCode(merge(e)))}),e.conversions){var s=this._conversionsToCode(e);s.length>0&&t.push(s)}return e.exceptions&&t.push(this._exceptions(e)),t.join("\n")},Node.prototype._exceptions=function(e){var t,r=[],n=e.prefix,s=e.args.length,i="arg"+e.args.length,o=Object.keys(this.childs),a=o.length>0?this.childs[o[0]]:void 0;if(a&&a.varArgs)t=e.refs.add(tooFewArguments,"err"),r.push(n+"throw "+t+"('"+a.type.types.join(",")+"', arguments.length); // Too few arguments");else if(0===o.length)r.push(n+"if (arguments.length > "+s+") {"),t=e.refs.add(tooManyArguments,"err"),r.push(n+" throw "+t+"("+s+", arguments.length); // Too many arguments"),r.push(n+"}");else if(-1===o.indexOf("any")){var u=e.refs.add(tooFewArguments,"err");r.push(n+"if (arguments.length === "+s+") {"),r.push(n+" throw "+u+"('"+o.join(",")+"', arguments.length); // Too few arguments"),r.push(n+"}");var p=e.refs.add(unexpectedType,"err");r.push(n+"throw "+p+"('"+o.join(",")+"', "+i+", "+s+"); // Unexpected type")}return r.join("\n")},Node.prototype._conversionsToCode=function(e){var t=[];return this._getConversions().forEach(function(r){var n=r.to,s=this.childs[n];t.push(s._toCode(e,r))}.bind(this)),t.join("\n")},Node.prototype._getChilds=function(){return Object.keys(this.childs).sort(compareTypes).map(function(e){return this.childs[e]}.bind(this))},Node.prototype._getConversions=function(){var e=this._getChilds().some(function(e){return e.type.varArgs});if(e)return[];var t={};return typed.conversions.filter(function(e){return void 0!==this.childs[e.from]||void 0===this.childs[e.to]||t[e.from]?!1:(t[e.from]=!0,!0)}.bind(this))},Node.prototype._getVarArgConversions=function(){var e={};return typed.conversions.filter(function(t){return-1!==this.type.types.indexOf(t.from)||-1===this.type.types.indexOf(t.to)||e[t.from]?!1:(e[t.from]=!0,!0)}.bind(this))},RootNode.prototype=Object.create(Node.prototype),RootNode.prototype.toCode=function(e){for(var t=[],r=this.depth(),n=[],s=0;r>s;s++)n[s]="arg"+s;var i=this.hasConversions();t.push("return function "+this.name+"("+n.join(", ")+") {");var o=this.fn?e.add(this.fn,"signature"):void 0;o&&(t.push(" if (arguments.length === 0) {"),t.push(" return "+o+"(); // signature: (empty)"),t.push(" }"));var a={refs:e,args:[],types:[],tests:[],prefix:" "};return i?(t.push(" // exactly matching signatures"),this._getChilds().forEach(function(e){t.push(e._toCode(merge(a,{conversions:!1,exceptions:!1})))}),t.push(" // convert into matching signatures"),this._getChilds().forEach(function(e){t.push(e._toCode(merge(a,{conversions:!0,exceptions:!0})))}),t.push(this._conversionsToCode(merge(a,{conversions:!0,exceptions:!0})))):(t.push(" // exactly matching signatures"),this._getChilds().forEach(function(e){t.push(e._toCode(merge(a,{conversions:!1,exceptions:!0})))})),t.push(this._exceptions(a)),t.push("}"),t.join("\n")};var types={"null":function(e){return null===e},undefined:function(e){return void 0===e},"boolean":function(e){return"boolean"==typeof e},number:function(e){return"number"==typeof e},string:function(e){return"string"==typeof e},"function":function(e){return"function"==typeof e},Array:function(e){return Array.isArray(e)},Date:function(e){return e instanceof Date},RegExp:function(e){return e instanceof RegExp},Object:function(e){return"object"==typeof e}},config={minify:!0},conversions=[],typed={config:config,types:types,conversions:conversions};return typed=_typed("typed",{Object:function(e){return _typed(null,e)},"string, Object":_typed,"string, function":function(e,t){var r={};return r[e]=t,_typed(null,r)},"string, string, function":function(e,t,r){var n={};return n[t]=r,_typed(e,n)}}),typed.config=config,typed.types=types,typed.conversions=conversions,typed}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
88173
17
1714
204
1