Poor man's RunRunRunTypes

node v10.24.1
version: 1.0.0
endpointsharetweet
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')
Loading…

no comments

    sign in to comment