Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

typed-function

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

typed-function - npm Package Compare versions

Comparing version 0.3.1 to 0.4.0

examples/multiple_signatures.js

16

examples/any_type.js
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)",

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc