Skip to content
This repository has been archived by the owner on Feb 16, 2021. It is now read-only.

Commit

Permalink
Merge pull request #15 from gcanti/3
Browse files Browse the repository at this point in the history
support for default values
  • Loading branch information
gcanti committed Apr 26, 2016
2 parents 81a2757 + a4c74c2 commit 5485830
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 115 deletions.
51 changes: 35 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

babel-plugin-tcomb is a babel plugin for runtime type checking using tcomb. You can annotate a function (arguments and return type) with tcomb types.

# Babel versions

- ^5.0.0 -> babel-plugin-tcomb ^0.1.0
- ^6.0.0 -> babel-plugin-tcomb ^0.2.0

# How it works

```js
Expand Down Expand Up @@ -37,10 +42,6 @@ foo(1, 'a'); // => ok
foo(1, 2); // => ...will throws "[tcomb] Invalid value 2 supplied to String"
```

# Caveats

- Destructuring and typed default values are not (yet) supported

# Setup

First, install via npm.
Expand All @@ -53,7 +54,7 @@ Then, in your babel configuration (usually in your .babelrc file), add "tcomb" t

```json
{
"plugins": ["tcomb"]
"plugins" : ["syntax-flow", "tcomb", "transform-flow-strip-types"]
}
```

Expand Down Expand Up @@ -84,6 +85,22 @@ module: {

# Features

**default values**

```js
function foo(x: t.Number, y = 1: t.Number) {
return x + y;
}

// compiles to
function foo(x: t.Number, y = 1: t.Number) {
t.assert(t.Number.is(x));
t.assert(t.Number.is(y));

return x + y;
}
```

**`struct` combinator**

```js
Expand All @@ -97,7 +114,7 @@ function foo(person: Person) {

// compiles to
function foo(person: Person) {
person = Person(person);
t.assert(Person.is(person));

return person.name;
}
Expand All @@ -114,7 +131,7 @@ function foo(x: Integer) {

// compiles to
function foo(x: Integer) {
x = Integer(x);
t.assert(Integer.is(x));

return x;
}
Expand All @@ -129,7 +146,7 @@ function foo(x: ?t.String) {

// compiles to
function foo(x: ?t.String) {
x = t.maybe(t.String)(x);
t.assert(t.maybe(t.String).is(x));

return x;
}
Expand All @@ -144,7 +161,7 @@ function foo(x: Array<t.String>) {

// compiles to
function foo(x: Array<t.String>) {
x = t.list(t.String)(x);
t.assert(t.list(t.String).is(x));

return x;
}
Expand All @@ -159,7 +176,7 @@ function foo(x: [t.String, t.Number]) {

// compiles to
function foo(x: [t.String, t.Number]) {
x = t.tuple([t.String, t.Number])(x);
t.assert(t.tuple([t.String, t.Number]).is(x));

return x;
}
Expand All @@ -174,7 +191,7 @@ function foo(x: t.String | t.Number) {

// compiles to
function foo(x: t.String | t.Number) {
x = t.union([t.String, t.Number])(x);
t.assert(t.union([t.String, t.Number]).is(x));

return x;
}
Expand All @@ -189,7 +206,7 @@ function foo(x: {[key: t.String]: t.Number}) {

// compiles to
function foo(x: { [key: t.String]: t.Number }) {
x = t.dict(t.String, t.Number)(x);
t.assert(t.dict(t.String, t.Number).is(x));

return x;
}
Expand All @@ -204,7 +221,7 @@ function foo(x: t.Number & t.String) {

// compiles to
function foo(x: t.Number & t.String) {
x = t.intersection([t.Number, t.String])(x);
t.assert(t.intersection([t.Number, t.String]).is(x));

return x;
}
Expand All @@ -217,7 +234,8 @@ const f = (x: t.String) => x;

// compiles to
const f = x => {
x = t.String(x);
t.assert(t.String.is(x));

return x;
};
```
Expand All @@ -234,13 +252,14 @@ class A {
// compiles to
class A {
foo(x: t.String): t.String {
x = t.String(x);
t.assert(t.String.is(x));

const ret = function (x) {
return x;
}(x);

return t.String(ret);
t.assert(t.String.is(ret));
return ret;
}
}
```
132 changes: 63 additions & 69 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
'use strict';

Object.defineProperty(exports, '__esModule', {
Object.defineProperty(exports, "__esModule", {
value: true
});

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }

var tcombLibraries = {
'tcomb': 1,
'tcomb-validation': 1,
'tcomb-react': 1,
'tcomb-form': 1
};

exports['default'] = function (_ref) {
var Plugin = _ref.Plugin;
exports.default = function (_ref) {
var t = _ref.types;


var tcombLocalName = 't';

function getExpressionFromGenericTypeAnnotation(id) {
Expand Down Expand Up @@ -82,7 +73,7 @@ exports['default'] = function (_ref) {
if (annotation.indexers.length === 1) {
return getDict(annotation.indexers[0].key, annotation.indexers[0].value);
}
throw new SyntaxError('[babel-plugin-tcomb] Unsupported Object type annotation');
throw new SyntaxError('Unsupported Object type annotation');

case 'IntersectionTypeAnnotation':
return getIntersection(annotation.types);
Expand All @@ -93,7 +84,7 @@ exports['default'] = function (_ref) {
}), annotation.returnType);

default:
throw new SyntaxError('[babel-plugin-tcomb] Unsupported type annotation: ' + annotation.type);
throw new SyntaxError('Unsupported type annotation: ' + annotation.type);
}
}

Expand All @@ -103,34 +94,35 @@ exports['default'] = function (_ref) {
return t.expressionStatement(assert);
}

function getFunctionArgumentChecks(node) {
function getFunctionArgumentCheckExpressions(node) {

function getTypeAnnotation(param) {
if (param.type === 'AssignmentPattern') {
if (param.left.typeAnnotation) {
throw new SyntaxError('[babel-plugin-tcomb] Typed default values are not supported');
if (param.typeAnnotation) {
if (param.type === 'AssignmentPattern') {
return { name: param.left.name, typeAnnotation: param.typeAnnotation };
}
return { name: param.name, typeAnnotation: param.typeAnnotation };
}
return param.typeAnnotation;
}

return node.params.filter(getTypeAnnotation).map(function (param) {
var id = t.identifier(param.name);
var typeAnnotation = getTypeAnnotation(param);
var _getTypeAnnotation = getTypeAnnotation(param);

var name = _getTypeAnnotation.name;
var typeAnnotation = _getTypeAnnotation.typeAnnotation;

var id = t.identifier(name);
return getAssert(typeAnnotation.typeAnnotation, id);
});
}

function getFunctionReturnTypeCheck(node) {
function getWrappedFunctionReturnWithTypeCheck(node) {
var params = node.params.map(function (param) {
return t.identifier(param.name);
});
var id = t.identifier('ret');

var isArrowExpression = node.type === 'ArrowFunctionExpression' && node.expression;
var body = isArrowExpression ? t.blockStatement([t.returnStatement(node.body)]) : node.body;

return [t.variableDeclaration('const', [t.variableDeclarator(id, t.callExpression(t.memberExpression(t.functionDeclaration(null, params, body), t.identifier('call')), [t.identifier('this')].concat(params)))]), getAssert(node.returnType.typeAnnotation, id), t.returnStatement(id)];
return [t.variableDeclaration('var', [t.variableDeclarator(id, t.callExpression(t.memberExpression(t.functionExpression(null, params, node.body), t.identifier('call')), [t.identifier('this')].concat(params)))]), getAssert(node.returnType.typeAnnotation, id), t.returnStatement(id)];
}

function getTcombLocalNameFromImports(node) {
Expand All @@ -141,57 +133,59 @@ exports['default'] = function (_ref) {
}
}

return new Plugin('tcomb', {
return {
visitor: {
ImportDeclaration: function ImportDeclaration(_ref2) {
var node = _ref2.node;

ImportDeclaration: {
exit: function exit(node) {
if (tcombLibraries.hasOwnProperty(node.source.value)) {
tcombLocalName = getTcombLocalNameFromImports(node);
}
if (tcombLibraries.hasOwnProperty(node.source.value)) {
tcombLocalName = getTcombLocalNameFromImports(node);
}
},
Function: function Function(path) {
var node = path.node;


Function: {
exit: function exit(node) {
try {

var body = getFunctionArgumentChecks(node);
if (node.returnType) {
body.push.apply(body, _toConsumableArray(getFunctionReturnTypeCheck(node)));
} else {
if (node.type === 'ArrowFunctionExpression' && node.expression) {
body.push(t.returnStatement(node.body));
} else {
body.push.apply(body, _toConsumableArray(node.body.body));
}
}

var ret = undefined;
if (node.type === 'FunctionDeclaration') {
ret = t.functionDeclaration(node.id, node.params, t.blockStatement(body));
} else if (node.type === 'FunctionExpression') {
ret = t.functionExpression(node.id, node.params, t.blockStatement(body));
} else if (node.type === 'ArrowFunctionExpression') {
ret = t.arrowFunctionExpression(node.params, t.blockStatement(body), false);
} else {
throw new SyntaxError('Unsupported function type');
}

ret.returnType = node.returnType;

return ret;
} catch (e) {
if (e instanceof SyntaxError) {
throw this.errorWithNode('[babel-plugin-tcomb] ' + e.message);
} else {
throw e;
}
try {
// Firstly let's replace arrow function expressions into
// block statement return structures.
if (node.type === "ArrowFunctionExpression" && node.expression) {
node.expression = false;
node.body = t.blockStatement([t.returnStatement(node.body)]);
}

// If we have a return type then we will wrap our entire function
// body and insert a type check on the returned value.
if (node.returnType) {
var funcBody = path.get('body');

funcBody.replaceWithMultiple(getWrappedFunctionReturnWithTypeCheck(node));
}

// Prepend any argument checks to the top of our function body.
var argumentChecks = getFunctionArgumentCheckExpressions(node);
if (argumentChecks.length > 0) {
var _node$body$body;

(_node$body$body = node.body.body).unshift.apply(_node$body$body, _toConsumableArray(argumentChecks));
}
} catch (e) {
if (e instanceof SyntaxError) {
throw new Error('[babel-plugin-tcomb] ' + e.message);
} else {
throw e;
}
}
}
}
});
};
};

module.exports = exports['default'];
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var tcombLibraries = {
'tcomb': 1,
'tcomb-validation': 1,
'tcomb-react': 1,
'tcomb-form': 1
};
Loading

0 comments on commit 5485830

Please sign in to comment.