babel-plugin-typecheck
Version:
Transforms flow type annotations into runtime type checks.
1,509 lines (1,354 loc) • 75.8 kB
JavaScript
'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) {