UNPKG

babel-plugin-typecheck

Version:

Transforms flow type annotations into runtime type checks.

1,509 lines (1,354 loc) 75.8 kB
'use strict'; var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); /** * # Typecheck Transformer */ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = function (_ref) { var t = _ref.types; var template = _ref.template; // constants used when statically verifying types var TYPE_INVALID = 0; var TYPE_VALID = 1; var TYPE_UNKNOWN = 2; /** * Binary Operators that can only produce boolean results. */ var BOOLEAN_BINARY_OPERATORS = ['==', '===', '>=', '<=', '>', '<', 'instanceof']; var checks = createChecks(); var staticChecks = createStaticChecks(); var checkIsArray = expression('Array.isArray(input)'); var checkIsMap = expression('input instanceof Map'); var checkIsSet = expression('input instanceof Set'); var checkIsObject = expression('input != null && typeof input === \'object\''); var checkNotNull = expression('input != null'); var checkEquals = expression('input === expected'); var declareTypeChecker = template('\n const id = function id (input) {\n return check;\n };\n '); var guard = template('\n if (!check) {\n throw new TypeError(message);\n }\n '); var thrower = template('\n if (check) {\n ret;\n }\n else {\n throw new TypeError(message);\n }\n '); var readableName = expression('\n input === null ? \'null\' : typeof input === \'object\' && input.constructor ? input.constructor.name || \'[Unknown Object]\' : typeof input\n '); var checkMapKeys = expression('\n input instanceof Map && Array.from(input.keys()).every(key => keyCheck)\n '); var checkMapValues = expression('\n input instanceof Map && Array.from(input.values()).every(value => valueCheck)\n '); var checkMapEntries = expression('\n input instanceof Map && Array.from(input).every(([key, value]) => keyCheck && valueCheck)\n '); var checkSetEntries = expression('\n input instanceof Set && Array.from(input).every(value => valueCheck)\n '); var PRAGMA_IGNORE_STATEMENT = /typecheck:\s*ignore\s+statement/i; var PRAGMA_IGNORE_FILE = /typecheck:\s*ignore\s+file/i; var visitors = { Statement: function Statement(path) { maybeSkip(path); }, TypeAlias: function TypeAlias(path) { if (maybeSkip(path)) { return; } path.replaceWith(createTypeAliasChecks(path)); }, InterfaceDeclaration: function InterfaceDeclaration(path) { if (maybeSkip(path)) { return; } path.replaceWith(createInterfaceChecks(path)); }, ExportNamedDeclaration: function ExportNamedDeclaration(path) { if (maybeSkip(path)) { return; } var node = path.node; var scope = path.scope; if (node.declaration && node.declaration.type === 'TypeAlias') { path.replaceWith(t.exportNamedDeclaration(createTypeAliasChecks(path.get('declaration')), [], null)); } }, ImportDeclaration: function ImportDeclaration(path) { if (maybeSkip(path)) { return; } if (path.node.importKind !== 'type') { return; } var _path$get$map$reduce = path.get('specifiers').map(function (specifier) { var local = specifier.get('local'); var tmpId = path.scope.generateUidIdentifierBasedOnNode(local.node); var replacement = t.importSpecifier(tmpId, specifier.node.imported); var id = t.identifier(local.node.name); id.isTypeChecker = true; var declarator = t.variableDeclarator(id, tmpId); declarator.isTypeChecker = true; return [declarator, replacement]; }).reduce(function (_ref2, _ref3) { var _ref5 = _slicedToArray(_ref2, 2); var declarators = _ref5[0]; var specifiers = _ref5[1]; var _ref4 = _slicedToArray(_ref3, 2); var declarator = _ref4[0]; var specifier = _ref4[1]; declarators.push(declarator); specifiers.push(specifier); return [declarators, specifiers]; }, [[], []]); var _path$get$map$reduce2 = _slicedToArray(_path$get$map$reduce, 2); var declarators = _path$get$map$reduce2[0]; var specifiers = _path$get$map$reduce2[1]; var declaration = t.variableDeclaration('var', declarators); declaration.isTypeChecker = true; path.replaceWithMultiple([t.importDeclaration(specifiers, path.node.source), declaration]); }, Function: { enter: function enter(path) { var _node$body$body; if (maybeSkip(path)) { return; } var node = path.node; var scope = path.scope; var paramChecks = collectParamChecks(path); if (node.type === "ArrowFunctionExpression" && node.expression) { node.expression = false; node.body = t.blockStatement([t.returnStatement(node.body)]); } (_node$body$body = node.body.body).unshift.apply(_node$body$body, _toConsumableArray(paramChecks)); node.savedTypeAnnotation = node.returnType; node.returnCount = 0; }, exit: function exit(path) { var node = path.node; var scope = path.scope; var isVoid = node.savedTypeAnnotation ? maybeNullableAnnotation(node.savedTypeAnnotation) : null; if (!node.returnCount && isVoid === false) { throw path.buildCodeFrameError('Function ' + (node.id ? '"' + node.id.name + '" ' : '') + 'did not return a value, expected ' + humanReadableType(node.savedTypeAnnotation, scope)); } } }, ReturnStatement: function ReturnStatement(path) { if (maybeSkip(path)) { return; } var node = path.node; var parent = path.parent; var scope = path.scope; var fn = path.getFunctionParent(); if (!fn) { return; } fn.node.returnCount++; if (node.isTypeChecked) { return; } var returnType = fn.node.returnType; if (!returnType) { return; } if (!node.argument) { if (maybeNullableAnnotation(returnType) === false) { throw path.buildCodeFrameError('Function ' + (fn.node.id ? '"' + fn.node.id.name + '" ' : '') + 'did not return a value, expected ' + humanReadableType(returnType, path.scope)); } return; } var id = undefined; if (node.argument.type === 'Identifier' || t.isLiteral(node.argument)) { id = node.argument; } else { id = scope.generateUidIdentifierBasedOnNode(node.argument); } var ok = staticCheckAnnotation(path.get("argument"), returnType); if (ok === true) { return; } else if (ok === false) { throw path.buildCodeFrameError('Invalid return type, expected ' + humanReadableType(returnType, scope)); } var check = checkAnnotation(id, returnType, scope); if (!check) { return; } if (parent.type !== 'BlockStatement' && parent.type !== 'Program') { var block = []; if (node.argument.type !== 'Identifier' && !t.isLiteral(node.argument)) { scope.push({ id: id }); block.push(t.expressionStatement(t.assignmentExpression('=', id, node.argument))); } var ret = t.returnStatement(id); ret.isTypeChecked = true; block.push(thrower({ check: check, ret: ret, message: returnTypeErrorMessage(path, fn.node, id) })); path.replaceWith(t.blockStatement(block)); } else { if (node.argument.type !== 'Identifier' && !t.isLiteral(node.argument)) { scope.push({ id: id }); path.insertBefore(t.expressionStatement(t.assignmentExpression('=', id, node.argument))); } var ret = t.returnStatement(id); ret.isTypeChecked = true; path.replaceWith(thrower({ check: check, ret: ret, message: returnTypeErrorMessage(path, fn.node, id) })); } }, VariableDeclaration: function VariableDeclaration(path) { if (maybeSkip(path)) { return; } var node = path.node; var scope = path.scope; var collected = []; var declarations = path.get("declarations"); for (var i = 0; i < node.declarations.length; i++) { var declaration = node.declarations[i]; var _id = declaration.id; var init = declaration.init; if (!_id.typeAnnotation || _id.hasBeenTypeChecked) { continue; } _id.savedTypeAnnotation = _id.typeAnnotation; _id.hasBeenTypeChecked = true; var ok = staticCheckAnnotation(declarations[i], _id.typeAnnotation); if (ok === true) { continue; } else if (ok === false) { throw path.buildCodeFrameError('Invalid assignment value, expected ' + humanReadableType(_id.typeAnnotation, scope)); } var check = checkAnnotation(_id, _id.typeAnnotation, scope); if (check) { collected.push(guard({ check: check, message: varTypeErrorMessage(_id, scope) })); } } if (collected.length > 0) { var check = collected.reduce(function (check, branch) { branch.alternate = check; return branch; }); if (path.parent.type === 'Program' || path.parent.type === 'BlockStatement') { path.insertAfter(check); } else if (path.parent.type === 'ExportNamedDeclaration' || path.parent.type === 'ExportDefaultDeclaration' || path.parent.type === 'ExportAllDeclaration' || path.parentPath.isForXStatement()) { path.parentPath.insertAfter(check); } else { path.replaceWith(t.blockStatement([node, check])); } } }, AssignmentExpression: function AssignmentExpression(path) { if (maybeSkip(path)) { return; } var node = path.node; var scope = path.scope; if (node.hasBeenTypeChecked || node.left.hasBeenTypeChecked || !t.isIdentifier(node.left)) { return; } var binding = scope.getBinding(node.left.name); if (!binding) { return; } else if (binding.path.type !== 'VariableDeclarator') { return; } var annotation = path.get('left').getTypeAnnotation(); if (annotation.type === 'AnyTypeAnnotation') { var item = binding.path.get('id'); annotation = item.node.savedTypeAnnotation || item.getTypeAnnotation(); } node.hasBeenTypeChecked = true; node.left.hasBeenTypeChecked = true; if (annotation.type === 'AnyTypeAnnotation') { annotation = getAnnotation(path.get('right')); if (allowsAny(annotation)) { return; } } var id = node.left; var right = path.get('right'); var ok = staticCheckAnnotation(right, annotation); if (ok === true) { return; } else if (ok === false) { throw path.buildCodeFrameError('Invalid assignment value, expected ' + humanReadableType(annotation, scope)); } var check = checkAnnotation(id, annotation, scope); if (!id.typeAnnotation) { id.typeAnnotation = annotation; } id.hasBeenTypeChecked = true; if (check) { path.getStatementParent().insertAfter(guard({ check: check, message: varTypeErrorMessage(id, scope) })); } }, TypeCastExpression: function TypeCastExpression(path) { var node = path.node; var target = undefined; switch (node.expression.type) { case 'Identifier': target = node.expression; break; case 'AssignmentExpression': target = node.expression.left; break; default: // unsupported. return; } var id = path.scope.getBindingIdentifier(target.name); if (!id) { return; } id.savedTypeAnnotation = path.getTypeAnnotation(); } }; return { visitor: { Program: function Program(path) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = path.get('body')[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var child = _step.value; if (maybeSkipFile(child)) { return; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } path.traverse(visitors); } } }; function createChecks() { return { number: expression('typeof input === \'number\''), numericLiteral: checkNumericLiteral, boolean: expression('typeof input === \'boolean\''), booleanLiteral: checkBooleanLiteral, function: expression('typeof input === \'function\''), string: expression('typeof input === \'string\''), stringLiteral: checkStringLiteral, symbol: expression('typeof input === \'symbol\''), undefined: expression('input === undefined'), null: expression('input === null'), void: expression('input == null'), instanceof: expression('input instanceof type'), type: expression('type(input)'), mixed: function mixed() { return null; }, any: function any() { return null; }, union: checkUnion, intersection: checkIntersection, array: checkArray, map: checkMap, set: checkSet, tuple: checkTuple, object: checkObject, nullable: checkNullable, typeof: checkTypeof }; } function createStaticChecks() { return { symbol: function symbol(path) { return maybeSymbolAnnotation(getAnnotation(path)); }, instanceof: function _instanceof(_ref6) { var path = _ref6.path; var type = _ref6.type; var node = path.node; var scope = path.scope; if (type.name === 'Object' && node.type === 'ObjectExpression' && !scope.hasBinding('Object')) { return true; } else if (type.name === 'Map' && !scope.hasBinding('Map')) { return null; } else if (type.name === 'Set' && !scope.hasBinding('Set')) { return null; } return maybeInstanceOfAnnotation(getAnnotation(path), type); }, type: (function (_type) { function type(_x) { return _type.apply(this, arguments); } type.toString = function () { return _type.toString(); }; return type; })(function (_ref7) { var path = _ref7.path; var type = _ref7.type; return null; }) }; } function compareAnnotations(a, b) { if (a.type === 'TypeAnnotation') { a = a.typeAnnotation; } if (b.type === 'TypeAnnotation') { b = b.typeAnnotation; } switch (a.type) { case 'StringTypeAnnotation': return maybeStringAnnotation(b); case 'StringLiteralTypeAnnotation': return compareStringLiteralAnnotations(a, b); case 'NumberTypeAnnotation': return maybeNumberAnnotation(b); case 'NumericLiteralTypeAnnotation': return compareNumericLiteralAnnotations(a, b); case 'BooleanTypeAnnotation': return maybeBooleanAnnotation(b); case 'BooleanLiteralTypeAnnotation': return compareBooleanLiteralAnnotations(a, b); case 'FunctionTypeAnnotation': return maybeFunctionAnnotation(b); case 'AnyTypeAnnotation': return null; case 'MixedTypeAnnotation': return null; case 'ObjectTypeAnnotation': return compareObjectAnnotation(a, b); case 'ArrayTypeAnnotation': return compareArrayAnnotation(a, b); case 'GenericTypeAnnotation': return compareGenericAnnotation(a, b); case 'TupleTypeAnnotation': return compareTupleAnnotation(a, b); case 'UnionTypeAnnotation': return compareUnionAnnotation(a, b); case 'IntersectionTypeAnnotation': return compareIntersectionAnnotation(a, b); case 'NullableTypeAnnotation': return compareNullableAnnotation(a, b); default: return null; } } function compareStringLiteralAnnotations(a, b) { if (b.type === 'StringLiteralTypeAnnotation') { return a.value === b.value; } else { return maybeStringAnnotation(b); } } function compareBooleanLiteralAnnotations(a, b) { if (b.type === 'BooleanLiteralTypeAnnotation') { return a.value === b.value; } else { return maybeBooleanAnnotation(b); } } function compareNumericLiteralAnnotations(a, b) { if (b.type === 'NumericLiteralTypeAnnotation') { return a.value === b.value; } else { return maybeNumberAnnotation(b); } } function unionComparer(a, b, comparator) { var falseCount = 0; var trueCount = 0; if (!a.types) { return null; } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = a.types[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var _type2 = _step2.value; var result = comparator(_type2, b); if (result === true) { if (b.type !== 'UnionTypeAnnotation') { return true; } trueCount++; } else if (result === false) { if (b.type === 'UnionTypeAnnotation') { return false; } falseCount++; } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } if (falseCount === a.types.length) { return false; } else if (trueCount === a.types.length) { return true; } else { return null; } } function intersectionComparer(a, b, comparator) { var falseCount = 0; var trueCount = 0; if (!a.types) { return null; } var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = a.types[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var _type3 = _step3.value; var result = comparator(_type3, b); if (result === true) { trueCount++; } else if (result === false) { return false; } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } if (trueCount === a.types.length) { return true; } else { return null; } } function compareObjectAnnotation(a, b) { switch (b.type) { case 'TypeAnnotation': case 'FunctonTypeParam': case 'NullableTypeAnnotation': return compareObjectAnnotation(a, b.typeAnnotation); case 'UnionTypeAnnotation': return unionComparer(a, b, compareObjectAnnotation); case 'IntersectionTypeAnnotation': return intersectionComparer(a, b, compareObjectAnnotation); case 'VoidTypeAnnotation': case 'BooleanTypeAnnotation': case 'BooleanLiteralTypeAnnotation': case 'StringTypeAnnotation': case 'StringLiteralTypeAnnotation': case 'NumberTypeAnnotation': case 'NumericLiteralTypeAnnotation': case 'FunctionTypeAnnotation': return false; default: return null; } } function compareArrayAnnotation(a, b) { switch (b.type) { case 'TypeAnnotation': case 'FunctonTypeParam': case 'NullableTypeAnnotation': return compareArrayAnnotation(a, b.typeAnnotation); case 'UnionTypeAnnotation': return unionComparer(a, b, compareArrayAnnotation); case 'IntersectionTypeAnnotation': return intersectionComparer(a, b, compareArrayAnnotation); case 'VoidTypeAnnotation': case 'BooleanTypeAnnotation': case 'BooleanLiteralTypeAnnotation': case 'StringTypeAnnotation': case 'StringLiteralTypeAnnotation': case 'NumberTypeAnnotation': case 'NumericLiteralTypeAnnotation': case 'FunctionTypeAnnotation': return false; default: return null; } } function compareGenericAnnotation(a, b) { switch (b.type) { case 'TypeAnnotation': case 'FunctonTypeParam': case 'NullableTypeAnnotation': return compareGenericAnnotation(a, b.typeAnnotation); case 'GenericTypeAnnotation': if (b.id.name === a.id.name) { return true; } else { return null; } case 'UnionTypeAnnotation': return unionComparer(a, b, compareGenericAnnotation); case 'IntersectionTypeAnnotation': return intersectionComparer(a, b, compareGenericAnnotation); default: return null; } } function compareTupleAnnotation(a, b) { if (b.type === 'TupleTypeAnnotation') { if (b.types.length === 0) { return null; } else if (b.types.length < a.types.length) { return false; } return a.types.every(function (type, index) { return compareAnnotations(type, b.types[index]); }); } switch (b.type) { case 'TypeAnnotation': case 'FunctonTypeParam': case 'NullableTypeAnnotation': return compareTupleAnnotation(a, b.typeAnnotation); case 'UnionTypeAnnotation': return unionComparer(a, b, compareTupleAnnotation); case 'IntersectionTypeAnnotation': return intersectionComparer(a, b, compareTupleAnnotation); case 'VoidTypeAnnotation': case 'BooleanTypeAnnotation': case 'BooleanLiteralTypeAnnotation': case 'StringTypeAnnotation': case 'StringLiteralTypeAnnotation': case 'NumberTypeAnnotation': case 'NumericLiteralTypeAnnotation': case 'FunctionTypeAnnotation': return false; default: return null; } } function compareUnionAnnotation(a, b) { switch (b.type) { case 'NullableTypeAnnotation': return compareUnionAnnotation(a, b.typeAnnotation); case 'AnyTypeAnnotation': case 'MixedTypeAnnotation': return null; default: return unionComparer(a, b, compareAnnotations); } } function compareNullableAnnotation(a, b) { switch (b.type) { case 'TypeAnnotation': case 'FunctonTypeParam': return compareNullableAnnotation(a, b.typeAnnotation); case 'NullableTypeAnnotation': case 'VoidTypeAnnotation': return null; } if (compareAnnotations(a.typeAnnotation, b) === true) { return true; } else { return null; } } function arrayExpressionToTupleAnnotation(path) { var elements = path.get('elements'); return t.tupleTypeAnnotation(elements.map(function (element) { return getAnnotation(element); })); } function checkNullable(_ref8) { var input = _ref8.input; var type = _ref8.type; var scope = _ref8.scope; var check = checkAnnotation(input, type, scope); if (!check) { return; } return t.logicalExpression("||", checks.void({ input: input }), check); } function checkTypeof(_ref9) { var input = _ref9.input; var annotation = _ref9.annotation; var scope = _ref9.scope; switch (annotation.type) { case 'GenericTypeAnnotation': var id = annotation.id; var path = Object.assign({}, input, { type: id.type, node: id, scope: scope }); return checkAnnotation(input, getAnnotation(path), scope); default: return checkAnnotation(input, annotation, scope); } } function checkStringLiteral(_ref10) { var input = _ref10.input; var annotation = _ref10.annotation; return checkEquals({ input: input, expected: t.stringLiteral(annotation.value) }); } function checkNumericLiteral(_ref11) { var input = _ref11.input; var annotation = _ref11.annotation; return checkEquals({ input: input, expected: t.numericLiteral(annotation.value) }); } function checkBooleanLiteral(_ref12) { var input = _ref12.input; var annotation = _ref12.annotation; return checkEquals({ input: input, expected: t.booleanLiteral(annotation.value) }); } function checkUnion(_ref13) { var input = _ref13.input; var types = _ref13.types; var scope = _ref13.scope; var checks = types.map(function (type) { return checkAnnotation(input, type, scope); }).filter(identity); return checks.reduce(function (last, check, index) { if (last == null) { return check; } return t.logicalExpression("||", last, check); }, null); } function checkIntersection(_ref14) { var input = _ref14.input; var types = _ref14.types; var scope = _ref14.scope; var checks = types.map(function (type) { return checkAnnotation(input, type, scope); }).filter(identity); return checks.reduce(function (last, check, index) { if (last == null) { return check; } return t.logicalExpression("&&", last, check); }, null); } function checkMap(_ref15) { var input = _ref15.input; var types = _ref15.types; var scope = _ref15.scope; var _types = _slicedToArray(types, 2); var keyType = _types[0]; var valueType = _types[1]; var key = t.identifier('key'); var value = t.identifier('value'); var keyCheck = keyType ? checkAnnotation(key, keyType, scope) : null; var valueCheck = valueType ? checkAnnotation(value, valueType, scope) : null; if (!keyCheck) { if (!valueCheck) { return checkIsMap({ input: input }); } else { return checkMapValues({ input: input, value: value, valueCheck: valueCheck }); } } else { if (!valueCheck) { return checkMapKeys({ input: input, key: key, keyCheck: keyCheck }); } else { return checkMapEntries({ input: input, key: key, value: value, keyCheck: keyCheck, valueCheck: valueCheck }); } } } function checkSet(_ref16) { var input = _ref16.input; var types = _ref16.types; var scope = _ref16.scope; var _types2 = _slicedToArray(types, 1); var valueType = _types2[0]; var value = t.identifier('value'); var valueCheck = valueType ? checkAnnotation(value, valueType, scope) : null; if (!valueCheck) { return checkIsSet({ input: input }); } else { return checkSetEntries({ input: input, value: value, valueCheck: valueCheck }); } } function checkArray(_ref17) { var input = _ref17.input; var types = _ref17.types; var scope = _ref17.scope; if (!types || types.length === 0) { return checkIsArray({ input: input }); } else if (types.length === 1) { var item = t.identifier('item'); var _type4 = types[0]; var check = checkAnnotation(item, _type4, scope); if (!check) { return checkIsArray({ input: input }); } return t.logicalExpression('&&', checkIsArray({ input: input }), t.callExpression(t.memberExpression(input, t.identifier('every')), [t.functionExpression(null, [item], t.blockStatement([t.returnStatement(check)]))])); } else { // This is a tuple var _checks = types.map(function (type, index) { return checkAnnotation(t.memberExpression(input, t.numericLiteral(index), true), type, scope); }).filter(identity); var checkLength = t.binaryExpression('>=', t.memberExpression(input, t.identifier('length')), t.numericLiteral(types.length)); return _checks.reduce(function (last, check, index) { return t.logicalExpression("&&", last, check); }, t.logicalExpression('&&', checkIsArray({ input: input }), checkLength)); } } function checkTuple(_ref18) { var input = _ref18.input; var types = _ref18.types; var scope = _ref18.scope; if (types.length === 0) { return checkIsArray({ input: input }); } // This is a tuple var checks = types.map(function (type, index) { return checkAnnotation(t.memberExpression(input, t.numericLiteral(index), true), type, scope); }).filter(identity); var checkLength = t.binaryExpression('>=', t.memberExpression(input, t.identifier('length')), t.numericLiteral(types.length)); return checks.reduce(function (last, check, index) { return t.logicalExpression("&&", last, check); }, t.logicalExpression('&&', checkIsArray({ input: input }), checkLength)); } function checkObject(_ref19) { var input = _ref19.input; var properties = _ref19.properties; var scope = _ref19.scope; var check = properties.reduce(function (expr, prop, index) { var target = t.memberExpression(input, prop.key); var check = checkAnnotation(target, prop.value, scope); if (check) { if (prop.optional) { check = t.logicalExpression('||', checks.undefined({ input: target }), check); } return t.logicalExpression("&&", expr, check); } else { return expr; } }, checkIsObject({ input: input })); return check; } function createTypeAliasChecks(path) { var node = path.node; var scope = path.scope; var id = node.id; var annotation = node.right; var input = t.identifier('input'); var check = checkAnnotation(input, annotation, scope) || t.booleanLiteral(true); var declaration = declareTypeChecker({ id: id, check: check }); declaration.isTypeChecker = true; return declaration; } function createInterfaceChecks(path) { var node = path.node; var scope = path.scope; var id = node.id; var annotation = node.body; var input = t.identifier('input'); var check = node.extends.reduce(function (check, extender) { return t.logicalExpression('&&', check, checkAnnotation(input, t.genericTypeAnnotation(extender.id), path.scope)); return check; }, checkAnnotation(input, annotation, scope) || t.booleanLiteral(true)); var declaration = declareTypeChecker({ id: id, check: check }); declaration.isTypeChecker = true; return declaration; } function checkAnnotation(input, annotation, scope) { switch (annotation.type) { case 'TypeAnnotation': case 'FunctonTypeParam': return checkAnnotation(input, annotation.typeAnnotation, scope); case 'TypeofTypeAnnotation': return checks.typeof({ input: input, annotation: annotation.argument, scope: scope }); case 'GenericTypeAnnotation': if (annotation.id.name === 'Array') { return checks.array({ input: input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope: scope }); } else if (annotation.id.name === 'Map' && !scope.hasBinding('Map')) { return checks.map({ input: input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope: scope }); } else if (annotation.id.name === 'Set' && !scope.hasBinding('Set')) { return checks.set({ input: input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope: scope }); } else if (annotation.id.name === 'Function') { return checks.function({ input: input }); } else if (annotation.id.name === 'Symbol') { return checks.symbol({ input: input }); } else if (isTypeChecker(annotation.id, scope)) { return checks.type({ input: input, type: annotation.id }); } else if (isPolymorphicType(annotation.id, scope)) { return; } else { return checks.instanceof({ input: input, type: createTypeExpression(annotation.id) }); } case 'TupleTypeAnnotation': return checks.tuple({ input: input, types: annotation.types, scope: scope }); case 'NumberTypeAnnotation': return checks.number({ input: input }); case 'NumericLiteralTypeAnnotation': return checks.numericLiteral({ input: input, annotation: annotation }); case 'BooleanTypeAnnotation': return checks.boolean({ input: input }); case 'BooleanLiteralTypeAnnotation': return checks.booleanLiteral({ input: input, annotation: annotation }); case 'StringTypeAnnotation': return checks.string({ input: input }); case 'StringLiteralTypeAnnotation': return checks.stringLiteral({ input: input, annotation: annotation }); case 'UnionTypeAnnotation': return checks.union({ input: input, types: annotation.types, scope: scope }); case 'IntersectionTypeAnnotation': return checks.intersection({ input: input, types: annotation.types, scope: scope }); case 'ObjectTypeAnnotation': return checks.object({ input: input, properties: annotation.properties, indexers: annotation.indexers, scope: scope }); case 'ArrayTypeAnnotation': return checks.array({ input: input, types: [annotation.elementType || t.anyTypeAnnotation()], scope: scope }); case 'FunctionTypeAnnotation': return checks.function({ input: input, params: annotation.params, returnType: annotation.returnType }); case 'MixedTypeAnnotation': return checks.mixed({ input: input }); case 'AnyTypeAnnotation': case 'ExistentialTypeParam': return checks.any({ input: input }); case 'NullableTypeAnnotation': return checks.nullable({ input: input, type: annotation.typeAnnotation, scope: scope }); case 'VoidTypeAnnotation': return checks.void({ input: input }); } } function staticCheckAnnotation(path, annotation) { var other = getAnnotation(path); switch (annotation.type) { case 'TypeAnnotation': case 'FunctonTypeParam': return staticCheckAnnotation(path, annotation.typeAnnotation); case 'GenericTypeAnnotation': if (isTypeChecker(annotation.id, path.scope)) { return staticChecks.type({ path: path, type: annotation.id }); } else if (isPolymorphicType(annotation.id, path.scope)) { return; } else if (annotation.id.name === 'Symbol') { return staticChecks.symbol(path); } else { return staticChecks.instanceof({ path: path, type: createTypeExpression(annotation.id) }); } } return compareAnnotations(annotation, other); } /** * Get the type annotation for a given node. */ function getAnnotation(path) { var annotation = undefined; try { annotation = getAnnotationShallow(path); } catch (e) {} while (annotation && annotation.type === 'TypeAnnotation') { annotation = annotation.typeAnnotation; } return annotation || t.anyTypeAnnotation(); } function getAnnotationShallow(path) { var node = path.node; var scope = path.scope; if (node.type === 'TypeAlias') { return node.right; } else if (!node.typeAnnotation && !node.savedTypeAnnotation && !node.returnType) { switch (path.type) { case 'Identifier': var id = scope.getBindingIdentifier(node.name); if (!id) { break; } if (id.savedTypeAnnotation) { return id.savedTypeAnnotation; } else if (id.returnType) { return id.returnType; } else if (id.typeAnnotation) { return id.typeAnnotation; } else if (isPolymorphicType(id, scope)) { return t.anyTypeAnnotation(); } return path.getTypeAnnotation(); case 'StringLiteral': case 'NumericLiteral': case 'BooleanLiteral': return createLiteralTypeAnnotation(path); case 'CallExpression': var callee = path.get('callee'); if (callee.type === 'Identifier') { if (callee.name === 'Symbol') { return t.genericTypeAnnotation('Symbol'); } var fn = getFunctionForIdentifier(callee); if (fn) { return getAnnotation(fn); } } break; case 'AssignmentExpression': return getAssignmentExpressionAnnotation(path); case 'MemberExpression': return getMemberExpressionAnnotation(path); case 'ArrayExpression': return getArrayExpressionAnnotation(path); case 'ObjectExpression': return getObjectExpressionAnnotation(path); case 'BinaryExpression': return getBinaryExpressionAnnotation(path); case 'BinaryExpression': return getBinaryExpressionAnnotation(path); case 'LogicalExpression': return getLogicalExpressionAnnotation(path); case 'ConditionalExpression': return getConditionalExpressionAnnotation(path); default: return path.getTypeAnnotation(); } } return node.savedTypeAnnotation || node.returnType || node.typeAnnotation || path.getTypeAnnotation(); } function createLiteralTypeAnnotation(path) { var annotation = undefined; if (path.isStringLiteral()) { annotation = t.stringLiteralTypeAnnotation(); } else if (path.isNumericLiteral()) { annotation = t.numericLiteralTypeAnnotation(); } else if (path.isBooleanLiteral()) { annotation = t.booleanLiteralTypeAnnotation(); } else { return path.getTypeAnnotation(); } annotation.value = path.node.value; return annotation; } function getObjectMethodAnnotation(path) { var node = path.node; return t.functionTypeAnnotation(null, node.params.map(function (param) { return param.savedTypeAnnotation || param.typeAnnotation; }), null, node.savedTypeAnnotation || node.returnType || node.typeAnnotation || t.anyTypeAnnotation()); } function getAssignmentExpressionAnnotation(path) { if (path.node.operator === '=') { return getAnnotation(path.get('right')); } } function getBinaryExpressionAnnotation(path) { var node = path.node; if (isBooleanExpression(node)) { return t.booleanTypeAnnotation(); } else { return t.anyTypeAnnotation(); } } function getLogicalExpressionAnnotation(path) { var node = path.node; if (isBooleanExpression(node)) { return t.booleanTypeAnnotation(); } else { var left = path.get('left'); var right = path.get('right'); switch (node.operator) { case '&&': case '||': var _ref20 = [getAnnotation(left), getAnnotation(right)]; left = _ref20[0]; right = _ref20[1]; if (t.isUnionTypeAnnotation(left)) { if (t.isUnionTypeAnnotation(right)) { return t.unionTypeAnnotation(left.types.concat(right.types)); } else { return t.unionTypeAnnotation(left.types.concat(right)); } } else { return t.unionTypeAnnotation([left, right]); } } return t.anyTypeAnnotation(); } } function getConditionalExpressionAnnotation(path) { var node = path.node; var consequent = getAnnotation(path.get('consequent')); var alternate = getAnnotation(path.get('alternate')); if (t.isUnionTypeAnnotation(consequent)) { if (t.isUnionTypeAnnotation(alternate)) { return t.unionTypeAnnotation(consequent.types.concat(alternate.types)); } else { return t.unionTypeAnnotation(consequent.types.concat(alternate)); } } else { return t.unionTypeAnnotation([consequent, alternate]); } } function getArrayExpressionAnnotation(path) { return t.genericTypeAnnotation(t.identifier('Array'), t.typeParameters(path.get('elements').map(getAnnotation))); } function getObjectExpressionAnnotation(path) { var annotation = t.objectTypeAnnotation(path.get('properties').map(function (property) { if (property.computed) { return; } switch (property.type) { case 'ObjectMethod': return t.objectTypeProperty(t.identifier(property.node.key.name), getObjectMethodAnnotation(property)); case 'ObjectProperty': return t.objectTypeProperty(t.identifier(property.node.key.name), property.node.value.savedTypeAnnotation || property.node.value.typeAnnotation || t.anyTypeAnnotation()); } }).filter(identity)); return annotation; } function getMemberExpressionAnnotation(path) { if (path.node.computed) { return getComputedMemberExpressionAnnotation(path); } var object = path.get('object'); var _path$get = path.get('property'); var id = _path$get.node; var name = id.name; var annotation = getAnnotation(object); if (annotation.type === 'NullableTypeAnnotation') { annotation = annotation.typeAnnotation; } if (annotation.type === 'GenericTypeAnnotation') { var target = path.scope.getBinding(annotation.id.name); if (target) { annotation = getAnnotation(target.path); } } switch (annotation.type) { case 'ObjectTypeAnnotation': var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = annotation.properties[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var _step4$value = _step4.value; var key = _step4$value.key; var value = _step4$value.value; if (key.name === id.name) { return value; } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } break; } return path.getTypeAnnotation(); } function getComputedMemberExpressionAnnotation(path) { var object = path.get('object'); var property = path.get('property'); var objectAnnotation = getAnnotation(object); if (objectAnnotation.type === 'TypeAnnotation' || objectAnnotation.type === 'NullableTypeAnnotation') { objectAnnotation = objectAnnotation.typeAnnotation; } var propertyAnnotation = getAnnotation(property); if (propertyAnnotation.type === 'TypeAnnotation' || propertyAnnotation.type === 'NullableTypeAnnotation') { propertyAnnotation = propertyAnnotation.typeAnnotation; } var _property$evaluate = property.evaluate(); var confident = _property$evaluate.confident; var value = _property$evaluate.value; if (!confident) { return path.getTypeAnnotation(); } switch (objectAnnotation.type) { case 'TupleTypeAnnotation': if (objectAnnotation.types.length === 0) { break; } else if (typeof value === 'number') { if (!objectAnnotation.types[value]) { throw path.buildCodeFrameError('Invalid computed member expression for tuple: ' + humanReadableType(objectAnnotation, path.scope)); } return objectAnnotation.types[value]; } else { throw path.buildCodeFrameError('Invalid computed member expression for tuple: ' + humanReadableType(objectAnnotation, path.scope)); } break; } return path.getTypeAnnotation(); } function getFunctionForIdentifier(path) { if (path.type !== 'Identifier') { return false; } var ref = path.scope.getBinding(path.node.name); if (!ref) { return false; } return t.isFunction(ref.path.parent) && ref.path.parentPath; } /** * Returns `true` if the annotation is definitely for an array, * otherwise `false`. */ function isStrictlyArrayAnnotation(annotation) { switch (annotation.type) { case 'TypeAnnotation': case 'FunctonTypeParam': return isStrictlyArrayAnnotation(annotation.typeAnnotation); case 'GenericTypeAnnotation': return annotation.id.name === 'Array'; case 'UnionTypeAnnotation': return annotation.types.every(isStrictlyArrayAnnotation); default: return false; } } function compareMaybeUnion(annotation, comparator) { var falseCount = 0; var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = annotation.types[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var _type5 = _step5.value; var result = comparator(_type5); if (result === true) { return true; } else if (result === false) { falseCount++; } } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } if (falseCount === annotation.types.length) { return false; } else { return null; } } /** * Returns `true` if the annotation is compatible with a number, * `false` if it definitely isn't, or `null` if we're not sure. */ function maybeNumberAnnotation(annotation) { switch (annotation.type) { case 'TypeAnnotation': case 'FunctonTypeParam': case 'NullableTypeAnnotation': return maybeNumberAnnotation(annotation.typeAnnotation); case 'NumberTypeAnnotation': case 'NumericLiteralTypeAnnotation': return true; case 'GenericTypeAnnotation': switch (annotation.id.name) { case 'Array': case 'Function': case 'Object': case 'String': case 'Boolean': case 'Date': case 'RegExp': return false; default: return null; } case 'UnionTypeAnnotation': return compareMaybeUnion(annotation, maybeNumberAnnotation); case 'AnyTypeAnnotation': case 'MixedTypeAnnotation': case 'IntersectionTypeAnnotation': return null; default: return false; } } /** * Returns `true` if the annotation is compatible with a string, * `false` if it definitely isn't, or `null` if we're not sure. */ function maybeStringAnnotation(annotation) { switch (annotation.type) { case 'TypeAnnotation': case 'FunctonTypeParam': case 'NullableTypeAnnotation': return maybeStringAnnotation(annotation.typeAnnotation); case 'StringTypeAnnotation': return true; case 'StringLiteralTypeAnnotation': return null; case 'GenericTypeAnnotation': switch (annotation.id.name) { case 'Array': case 'Function': case 'Object': case 'Number': case 'Boolean': case 'Date': case 'RegExp': return false; default: return null; } case 'UnionTypeAnnotation': var falseCount = 0; var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = annotation.types[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var _type6 = _step6.value; var result = maybeStringAnnotation(_type6); if (result === true) { return true; } else if (result === false) { falseCount++; } } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } if (falseCount === annotation.types.length) {