Poor man's RunRunRunTypes
const { parse } = require('@babel/parser');
const makeInvariantFromNode = node => {
switch (node.type) {
case 'StringLiteralTypeAnnotation':
case 'NumberLiteralTypeAnnotation': {
const { value } = node;
return x => x === value;
}
case 'StringTypeAnnotation':
return x => typeof x === 'string';
case 'NumberTypeAnnotation':
return x => typeof x === 'number';
case 'NullLiteralTypeAnnotation':
return x => x === null;
case 'VoidTypeAnnotation':
return x => x === undefined;
case 'NullableTypeAnnotation': {
const inner = makeInvariantFromNode(node.typeAnnotation);
return val => val === null || val === undefined || inner(val);
}
case 'ArrayTypeAnnotation': {
const inner = makeInvariantFromNode(node.elementType);
return val => Array.isArray(val) && val.every(inner);
}
case 'GenericTypeAnnotation': {
const { name } = node.id;
return val => typeof val === 'object' && val.constructor.name === name;
}
case 'IntersectionTypeAnnotation': {
const inner = node.types.map(makeInvariantFromNode);
return val => inner.every(i => i(val));
}
case 'UnionTypeAnnotation': {
const inner = node.types.map(makeInvariantFromNode);
return val => inner.some(i => i(val));
}
case 'ObjectTypeAnnotation': {
const properties = node.properties
.filter(n => n.type === 'ObjectTypeProperty')
.map(n => [n.key.name, makeInvariantFromNode(n.value)]);
return val => typeof val === 'object' && properties.every(([key, inner]) => inner(val[key]));
}
default:
return () => true;
}
};
const makeInvariantForFn = (paramTypes, returnType) => fn => {
const paramSize = paramTypes.length;
const paramInvariants = paramTypes.map(makeInvariantFromNode);
const returnInvariant = makeInvariantFromNode(returnType);
return (...args) => {
for (let i = 0; i < paramSize; i++) {
if (!paramInvariants[i](args[i])) {
const typeReadable = paramTypes[i].type.replace('TypeAnnotation', '');
throw new TypeError(`Invalid ${i + 1}. argument, expected ${typeReadable}.`);
}
}
const res = fn(...args);
if (!returnInvariant(res)) {
const typeReadable = returnType.type.replace('TypeAnnotation', '');
throw new TypeError(`Invalid return value, expected ${typeReadable}.`);
}
return res;
};
};
const sig = def => {
const input = `type x = ${def}`;
const { program: { body } } = parse(input, { plugins: ['flow'] });
const node = body[0];
if (!node || !node.right || node.right.type !== 'FunctionTypeAnnotation') {
throw new TypeError(`Expected valid Flow function annotation.`);
}
const { params, returnType } = node.right;
console.log(returnType);
const paramTypes = params.map(n => n.typeAnnotation);
return makeInvariantForFn(paramTypes, returnType);
};
const fun = sig`(string) => [1, 2, 3]`((a, b) => [1, 2, 3, 4])
fun('test', 'test')
no comments