UNPKG

87.4 kBJavaScriptView Raw
1import generate from "babel-generator";
2
3type Node = {
4 type: string;
5};
6
7type Literal = {
8 type: 'StringLiteral' | 'BooleanLiteral' | 'NumericLiteral' | 'NullLiteral' | 'RegExpLiteral'
9};
10
11type Identifier = {
12 type: string;
13 name: string;
14};
15
16type QualifiedTypeIdentifier = {
17 id: Identifier;
18 qualification: Identifier|QualifiedTypeIdentifier;
19};
20
21type TypeAnnotation = {
22 type: string;
23};
24
25interface StringLiteralTypeAnnotation extends TypeAnnotation {
26 type: 'StringLiteralTypeAnnotation';
27}
28
29interface NumericLiteralTypeAnnotation extends TypeAnnotation {
30 type: 'NumericLiteralTypeAnnotation';
31}
32
33interface BooleanLiteralTypeAnnotation extends TypeAnnotation {
34 type: 'BooleanLiteralTypeAnnotation';
35}
36
37type Scope = {};
38
39type NodePath = {
40 type: string;
41 node: Node;
42 scope: Scope;
43};
44
45/**
46 * # Typecheck Transformer
47 */
48export default function ({types: t, template}): Object {
49
50 /**
51 * Binary Operators that can only produce boolean results.
52 */
53 const BOOLEAN_BINARY_OPERATORS: string[] = [
54 '==',
55 '===',
56 '>=',
57 '<=',
58 '>',
59 '<',
60 'instanceof'
61 ];
62
63 const checks: Object = createChecks();
64 const staticChecks: Object = createStaticChecks();
65
66 const checkIsArray: (() => Node) = expression(`Array.isArray(input)`);
67 const checkIsMap: (() => Node) = expression(`input instanceof Map`);
68 const checkIsSet: (() => Node) = expression(`input instanceof Set`);
69 const checkIsGenerator: (() => Node) = expression(`typeof input === 'function' && input.generator`);
70 const checkIsIterable: (() => Node) = expression(`input && typeof input[Symbol.iterator] === 'function'`);
71 const checkIsObject: (() => Node) = expression(`input != null && typeof input === 'object'`);
72 const checkNotNull: (() => Node) = expression(`input != null`);
73 const checkEquals: (() => Node) = expression(`input === expected`);
74
75 const declareTypeChecker: (() => Node) = template(`
76 const id = function id (input) {
77 return check;
78 };
79 `);
80
81 const guard: (() => Node) = template(`
82 if (!check) {
83 throw new TypeError(message);
84 }
85 `);
86
87 const thrower: (() => Node) = template(`
88 if (check) {
89 ret;
90 }
91 else {
92 throw new TypeError(message);
93 }
94 `);
95
96 const guardInline: (() => Node) = expression(`
97 (id => {
98 if (!check) {
99 throw new TypeError(message);
100 }
101 return id;
102 })(input)
103 `);
104
105 const guardFn: (() => Node) = expression(`
106 function name (id) {
107 if (!check) {
108 throw new TypeError(message);
109 }
110 return id;
111 }
112 `);
113
114 const readableName: (() => Node) = expression(`
115 input === null ? 'null' : typeof input === 'object' && input.constructor ? input.constructor.name || '[Unknown Object]' : typeof input
116 `);
117
118 const checkMapKeys: (() => Node) = expression(`
119 input instanceof Map && Array.from(input.keys()).every(key => keyCheck)
120 `);
121
122 const checkMapValues: (() => Node) = expression(`
123 input instanceof Map && Array.from(input.values()).every(value => valueCheck)
124 `);
125
126 const checkMapEntries: (() => Node) = expression(`
127 input instanceof Map && Array.from(input).every(([key, value]) => keyCheck && valueCheck)
128 `);
129
130 const checkSetEntries: (() => Node) = expression(`
131 input instanceof Set && Array.from(input).every(value => valueCheck)
132 `);
133
134 const PRAGMA_IGNORE_STATEMENT = /typecheck:\s*ignore\s+statement/i;
135 const PRAGMA_IGNORE_FILE = /typecheck:\s*ignore\s+file/i;
136
137 const visitors = {
138 Statement (path: NodePath): void {
139 maybeSkip(path);
140 },
141 TypeAlias (path: NodePath): void {
142 if (maybeSkip(path)) {
143 return;
144 }
145 path.replaceWith(createTypeAliasChecks(path));
146 },
147
148 InterfaceDeclaration (path: NodePath): void {
149 if (maybeSkip(path)) {
150 return;
151 }
152 path.replaceWith(createInterfaceChecks(path));
153 },
154
155 ExportNamedDeclaration (path: NodePath): void {
156 if (maybeSkip(path)) {
157 return;
158 }
159 const {node, scope} = path;
160 if (node.declaration && node.declaration.type === 'TypeAlias') {
161 path.replaceWith(t.exportNamedDeclaration(
162 createTypeAliasChecks(path.get('declaration')),
163 [],
164 null
165 ));
166 }
167 },
168
169 ImportDeclaration (path: NodePath): void {
170 if (maybeSkip(path)) {
171 return;
172 }
173 if (path.node.importKind !== 'type') {
174 return;
175 }
176 const [declarators, specifiers] = path.get('specifiers')
177 .map(specifier => {
178 const local = specifier.get('local');
179 const tmpId = path.scope.generateUidIdentifierBasedOnNode(local.node);
180 const replacement = t.importSpecifier(tmpId, specifier.node.imported);
181 const id = t.identifier(local.node.name);
182
183 id.isTypeChecker = true;
184 const declarator = t.variableDeclarator(id, tmpId);
185 declarator.isTypeChecker = true;
186 return [declarator, replacement];
187 })
188 .reduce(([declarators, specifiers], [declarator, specifier]) => {
189 declarators.push(declarator);
190 specifiers.push(specifier);
191 return [declarators, specifiers];
192 }, [[], []]);
193
194 const declaration = t.variableDeclaration('var', declarators);
195 declaration.isTypeChecker = true;
196
197 path.replaceWithMultiple([
198 t.importDeclaration(specifiers, path.node.source),
199 declaration
200 ]);
201 },
202
203 Function: {
204 enter (path: NodePath): void {
205 if (maybeSkip(path)) {
206 return;
207 }
208
209 const {node, scope} = path;
210 const paramChecks = collectParamChecks(path);
211 if (node.type === "ArrowFunctionExpression" && node.expression) {
212 node.expression = false;
213 node.body = t.blockStatement([t.returnStatement(node.body)]);
214 }
215 if (node.returnType) {
216 createFunctionReturnGuard(path);
217 createFunctionYieldGuards(path);
218 }
219 node.body.body.unshift(...paramChecks);
220 node.savedTypeAnnotation = node.returnType;
221 node.returnCount = 0;
222 node.yieldCount = 0;
223 },
224 exit (path: NodePath): void {
225 const {node, scope} = path;
226 const isVoid = node.savedTypeAnnotation ? maybeNullableAnnotation(node.savedTypeAnnotation) : null;
227 if (!node.returnCount && isVoid === false) {
228 let annotation = node.savedTypeAnnotation;
229 if (annotation.type === 'TypeAnnotation') {
230 annotation = annotation.typeAnnotation;
231 }
232 if (node.generator && isGeneratorAnnotation(annotation) && annotation.typeParameters && annotation.typeParameters.params.length > 1) {
233 annotation = annotation.typeParameters.params[1];
234 }
235 throw path.buildCodeFrameError(`Function ${node.id ? `"${node.id.name}" ` : ''}did not return a value, expected ${humanReadableType(annotation)}`);
236 }
237 if (node.nextGuardCount) {
238 path.get('body').get('body')[0].insertBefore(node.nextGuard);
239 }
240 if (node.yieldGuardCount) {
241 path.get('body').get('body')[0].insertBefore(node.yieldGuard);
242 }
243 if (node.returnGuardCount) {
244 path.get('body').get('body')[0].insertBefore(node.returnGuard);
245 }
246 }
247 },
248
249 YieldExpression (path: NodePath): void {
250 const fn = path.getFunctionParent();
251 if (!fn) {
252 return;
253 }
254 fn.node.yieldCount++;
255 if (!isGeneratorAnnotation(fn.node.returnType) || maybeSkip(path)) {
256 return;
257 }
258 const {node, parent, scope} = path;
259 let annotation = fn.node.returnType;
260 if (annotation.type === 'NullableTypeAnnotation' || annotation.type === 'TypeAnnotation') {
261 annotation = annotation.typeAnnotation;
262 }
263 if (!annotation.typeParameters || annotation.typeParameters.params.length === 0) {
264 return;
265 }
266
267 const yieldType = annotation.typeParameters.params[0];
268 const nextType = annotation.typeParameters.params[2];
269 const ok = staticCheckAnnotation(path.get("argument"), yieldType);
270 if (ok === true && !nextType) {
271 return;
272 }
273 else if (ok === false) {
274 throw path.buildCodeFrameError(`Function ${fn.node.id ? `"${fn.node.id.name}" ` : ''}yielded an invalid type, expected ${humanReadableType(yieldType)} got ${humanReadableType(getAnnotation(path.get('argument')))}`);
275 }
276 fn.node.yieldGuardCount++;
277 if (fn.node.yieldGuard) {
278 const yielder = t.yieldExpression(
279 t.callExpression(fn.node.yieldGuardName, [node.argument || t.identifier('undefined')])
280 );
281 yielder.hasBeenTypeChecked = true;
282
283 if (fn.node.nextGuard) {
284 fn.node.nextGuardCount++;
285 path.replaceWith(t.callExpression(fn.node.nextGuardName, [yielder]));
286 }
287 else {
288 path.replaceWith(yielder);
289 }
290 }
291 else if (fn.node.nextGuard) {
292 fn.node.nextGuardCount++;
293 path.replaceWith(t.callExpression(fn.node.nextGuardName, [yielder]));
294 }
295 },
296
297
298 ReturnStatement (path: NodePath): void {
299 const fn = path.getFunctionParent();
300 if (!fn) {
301 return;
302 }
303 fn.node.returnCount++;
304 if (maybeSkip(path)) {
305 return;
306 }
307 const {node, parent, scope} = path;
308 const {returnType, returnGuardName} = fn.node;
309 if (!returnType || !returnGuardName) {
310 return;
311 }
312 if (!node.argument) {
313 if (maybeNullableAnnotation(returnType) === false) {
314 throw path.buildCodeFrameError(`Function ${fn.node.id ? `"${fn.node.id.name}" ` : ''}did not return a value, expected ${humanReadableType(returnType)}`);
315 }
316 return;
317 }
318 let annotation = returnType;
319 if (annotation.type === 'TypeAnnotation') {
320 annotation = annotation.typeAnnotation;
321 }
322 if (isGeneratorAnnotation(annotation)) {
323 annotation = annotation.typeParameters && annotation.typeParameters.params.length > 1 ? annotation.typeParameters.params[1] : t.anyTypeAnnotation();
324 }
325 const ok = staticCheckAnnotation(path.get("argument"), annotation);
326 if (ok === true) {
327 return;
328 }
329 else if (ok === false) {
330 throw path.buildCodeFrameError(`Function ${fn.node.id ? `"${fn.node.id.name}" ` : ''}returned an invalid type, expected ${humanReadableType(annotation)} got ${humanReadableType(getAnnotation(path.get('argument')))}`);
331 }
332 fn.node.returnGuardCount++;
333 const returner = t.returnStatement(t.callExpression(fn.node.returnGuardName, [node.argument]));
334 returner.hasBeenTypeChecked = true;
335 path.replaceWith(returner);
336 },
337
338 VariableDeclaration (path: NodePath): void {
339 if (maybeSkip(path)) {
340 return;
341 }
342 const {node, scope} = path;
343 const collected = [];
344 const declarations = path.get("declarations");
345 for (let i = 0; i < node.declarations.length; i++) {
346 const declaration = node.declarations[i];
347 const {id, init} = declaration;
348 if (!id.typeAnnotation || id.hasBeenTypeChecked) {
349 continue;
350 }
351 id.savedTypeAnnotation = id.typeAnnotation;
352 id.hasBeenTypeChecked = true;
353 const ok = staticCheckAnnotation(declarations[i], id.typeAnnotation);
354 if (ok === true) {
355 continue;
356 }
357 else if (ok === false) {
358 throw path.buildCodeFrameError(`Invalid assignment value, expected ${humanReadableType(id.typeAnnotation)} got ${humanReadableType(getAnnotation(declarations[i]))}`);
359 }
360 const check = checkAnnotation(id, id.typeAnnotation, scope);
361 if (check) {
362 collected.push(guard({
363 check,
364 message: varTypeErrorMessage(id, scope)
365 }));
366 }
367 }
368 if (collected.length > 0) {
369 const check = collected.reduce((check, branch) => {
370 branch.alternate = check;
371 return branch;
372 });
373 if (path.parent.type === 'Program' || path.parent.type === 'BlockStatement') {
374 path.insertAfter(check);
375 }
376 else if (path.parentPath.isForXStatement() || path.parentPath.isForStatement() || path.parentPath.isForInStatement()) {
377 let body = path.parentPath.get('body');
378 if (body.type !== 'BlockStatement') {
379 const block = t.blockStatement([body.node]);
380 body.replaceWith(block);
381 body = path.parentPath.get('body');
382 }
383 const children = body.get('body');
384 if (children.length === 0) {
385 body.replaceWith(check);
386 }
387 else {
388 children[0].insertBefore(check);
389 }
390
391 }
392 else if (path.parent.type === 'ExportNamedDeclaration' || path.parent.type === 'ExportDefaultDeclaration' || path.parent.type === 'ExportAllDeclaration') {
393 path.parentPath.insertAfter(check);
394 }
395 else {
396 path.replaceWith(t.blockStatement([node, check]));
397 }
398 }
399 },
400
401 AssignmentExpression (path: NodePath): void {
402 if (maybeSkip(path)) {
403 return;
404 }
405 const {node, scope} = path;
406 const left = path.get('left');
407 let annotation;
408 if (node.hasBeenTypeChecked || node.left.hasBeenTypeChecked) {
409 return;
410 }
411 else if (left.isMemberExpression()) {
412 annotation = getAnnotation(left);
413 }
414 else if (t.isIdentifier(node.left)) {
415 const binding = scope.getBinding(node.left.name);
416 if (!binding) {
417 return;
418 }
419 else if (binding.path.type !== 'VariableDeclarator') {
420 return;
421 }
422 annotation = left.getTypeAnnotation();
423 if (annotation.type === 'AnyTypeAnnotation') {
424 const item = binding.path.get('id');
425 annotation = item.node.savedTypeAnnotation || item.getTypeAnnotation();
426 }
427 }
428 else {
429 return;
430 }
431
432 node.hasBeenTypeChecked = true;
433 node.left.hasBeenTypeChecked = true;
434 const id = node.left;
435 const right = path.get('right');
436 if (annotation.type === 'AnyTypeAnnotation') {
437 annotation = getAnnotation(right);
438 if (allowsAny(annotation)) {
439 return;
440 }
441 }
442 const ok = staticCheckAnnotation(right, annotation);
443 if (ok === true) {
444 return;
445 }
446 else if (ok === false) {
447 throw path.buildCodeFrameError(`Invalid assignment value, expected ${humanReadableType(annotation)} got ${humanReadableType(getAnnotation(right))}`);
448 }
449 const check = checkAnnotation(id, annotation, scope);
450 if (!id.typeAnnotation) {
451 id.typeAnnotation = annotation;
452 }
453 id.hasBeenTypeChecked = true;
454 if (check) {
455 const parent = path.getStatementParent();
456 parent.insertAfter(guard({
457 check,
458 message: varTypeErrorMessage(id, scope)
459 }));
460 }
461 },
462
463 TypeCastExpression (path: NodePath): void {
464 const {node} = path;
465 let target;
466 switch (node.expression.type) {
467 case 'Identifier':
468 target = node.expression;
469 break;
470 case 'AssignmentExpression':
471 target = node.expression.left;
472 break;
473 default:
474 // unsupported.
475 return;
476 }
477 const id = path.scope.getBindingIdentifier(target.name);
478 if (!id) {
479 return;
480 }
481 id.savedTypeAnnotation = path.getTypeAnnotation();
482 },
483
484 ForOfStatement (path: NodePath): void {
485 if (maybeSkip(path)) {
486 return;
487 }
488 const left: NodePath = path.get('left');
489 const right: NodePath = path.get('right');
490 const rightAnnotation: TypeAnnotation = getAnnotation(right);
491 const leftAnnotation: TypeAnnotation = left.isVariableDeclaration() ? getAnnotation(left.get('declarations')[0].get('id')) : getAnnotation(left);
492 if (rightAnnotation.type !== 'VoidTypeAnnotation') {
493 const ok: ?boolean = maybeIterableAnnotation(rightAnnotation);
494 if (ok === false) {
495 throw path.buildCodeFrameError(`Cannot iterate ${humanReadableType(rightAnnotation)}`);
496 }
497 }
498 let id: ?Identifier;
499 if (right.isIdentifier()) {
500 id = right.node;
501 }
502 else {
503 id = path.scope.generateUidIdentifierBasedOnNode(right.node);
504 path.scope.push({id});
505 const replacement: Node = t.expressionStatement(t.assignmentExpression('=', id, right.node));
506 path.insertBefore(replacement);
507 right.replaceWith(id);
508 }
509 path.insertBefore(guard({
510 check: checks.iterable({input: id}),
511 message: t.binaryExpression(
512 '+',
513 t.stringLiteral(`Expected ${generate(right.node).code} to be iterable, got `),
514 readableName({input: id})
515 )
516 }));
517
518 if (rightAnnotation.type !== 'GenericTypeAnnotation' || rightAnnotation.id.name !== 'Iterable' || !rightAnnotation.typeParameters || !rightAnnotation.typeParameters.params.length) {
519 return;
520 }
521
522 const annotation: TypeAnnotation = rightAnnotation.typeParameters.params[0];
523 if (compareAnnotations(annotation, leftAnnotation) === false) {
524 throw path.buildCodeFrameError(`Invalid iterator type, expected ${humanReadableType(annotation)} got ${humanReadableType(leftAnnotation)}`);
525 }
526 }
527 };
528
529
530 return {
531 visitor: {
532 Program (path: NodePath) {
533 for (let child of path.get('body')) {
534 if (maybeSkipFile(child)) {
535 return;
536 }
537 }
538 path.traverse(visitors);
539 }
540 }
541 }
542
543 /**
544 * Create a function which can verify the return type for a function.
545 */
546 function createFunctionReturnGuard (path: NodePath): void {
547 const {node, scope} = path;
548 let annotation = node.returnType;
549 if (annotation.type === 'TypeAnnotation') {
550 annotation = annotation.typeAnnotation;
551 }
552 if (isGeneratorAnnotation(annotation)) {
553 annotation = annotation.typeParameters && annotation.typeParameters.params.length > 1 ? annotation.typeParameters.params[1] : t.anyTypeAnnotation();
554 }
555 const name = scope.generateUidIdentifierBasedOnNode(node);
556 const id = scope.generateUidIdentifier('id');
557 const check = checkAnnotation(id, annotation, scope);
558 if (check) {
559 node.returnGuard = guardFn({
560 id,
561 name,
562 check,
563 message: returnTypeErrorMessage(path, path.node, id)
564 });
565 node.returnGuard.hasBeenTypeChecked = true;
566 node.returnGuardName = name;
567 node.returnGuardCount = 0;
568 }
569 }
570
571 function createFunctionYieldGuards (path: NodePath) {
572 const {node, scope} = path;
573 let annotation = node.returnType;
574 if (annotation.type === 'NullableTypeAnnotation' || annotation.type === 'TypeAnnotation') {
575 annotation = annotation.typeAnnotation;
576 }
577 if (!annotation.typeParameters || annotation.typeParameters.params.length === 0) {
578 return;
579 }
580 if (annotation.type === 'TypeAnnotation') {
581 annotation = annotation.typeAnnotation;
582 }
583 if (!isGeneratorAnnotation(annotation)) {
584 return;
585 }
586
587 const yieldType = annotation.typeParameters.params[0];
588 const nextType = annotation.typeParameters.params[2];
589
590 if (yieldType) {
591 const name = scope.generateUidIdentifier(`check${node.id ? node.id.name.slice(0, 1).toUpperCase() + node.id.name.slice(1) : ''}Yield`);
592 const id = scope.generateUidIdentifier('id');
593 const check = checkAnnotation(id, yieldType, scope);
594 if (check) {
595 node.yieldGuard = guardFn({
596 id,
597 name,
598 check,
599 message: yieldTypeErrorMessage(node, yieldType, id)
600 });
601 node.yieldGuardName = name;
602 node.yieldGuardCount = 0;
603 }
604 }
605
606
607 if (nextType) {
608 const name = scope.generateUidIdentifier(`check${node.id ? node.id.name.slice(0, 1).toUpperCase() + node.id.name.slice(1) : ''}Next`);
609 const id = scope.generateUidIdentifier('id');
610 const check = checkAnnotation(id, nextType, scope);
611 if (check) {
612 node.nextGuard = guardFn({
613 id,
614 name,
615 check,
616 message: yieldNextTypeErrorMessage(node, nextType, id)
617 });
618 node.nextGuardName = name;
619 node.nextGuardCount = 0;
620 }
621 }
622 }
623
624 function isThisMemberExpression (path: NodePath): boolean {
625 const {node} = path;
626 if (node.type === 'ThisExpression') {
627 return true;
628 }
629 else if (node.type === 'MemberExpression') {
630 return isThisMemberExpression(path.get('object'));
631 }
632 else {
633 return false;
634 }
635 }
636
637 function isGeneratorAnnotation (annotation: ?TypeAnnotation): boolean {
638 if (!annotation) {
639 return false;
640 }
641 if (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation') {
642 annotation = annotation.typeAnnotation;
643 }
644 return annotation.type === 'GenericTypeAnnotation' && annotation.id.name === 'Generator';
645 }
646
647 function createChecks (): Object {
648 return {
649 number: expression(`typeof input === 'number'`),
650 numericLiteral: checkNumericLiteral,
651 boolean: expression(`typeof input === 'boolean'`),
652 booleanLiteral: checkBooleanLiteral,
653 function: expression(`typeof input === 'function'`),
654 string: expression(`typeof input === 'string'`),
655 stringLiteral: checkStringLiteral,
656 symbol: expression(`typeof input === 'symbol'`),
657 undefined: expression(`input === undefined`),
658 null: expression(`input === null`),
659 void: expression(`input == null`),
660 instanceof: expression(`input instanceof type`),
661 type: expression(`type(input)`),
662 mixed: () => null,
663 any: () => null,
664 union: checkUnion,
665 intersection: checkIntersection,
666 array: checkArray,
667 map: checkMap,
668 set: checkSet,
669 generator: checkGenerator,
670 iterable: checkIterable,
671 tuple: checkTuple,
672 object: checkObject,
673 nullable: checkNullable,
674 typeof: checkTypeof
675 };
676 }
677
678 function createStaticChecks (): Object {
679 return {
680 symbol (path: NodePath): ?boolean {
681 return maybeSymbolAnnotation(getAnnotation(path));
682 },
683 instanceof ({path, annotation}): ?boolean {
684 const type = createTypeExpression(annotation.id);
685
686 const {node, scope} = path;
687 if (type.name === 'Object' && node.type === 'ObjectExpression' && !scope.hasBinding('Object')) {
688 return true;
689 }
690 else if (type.name === 'Map' && !scope.hasBinding('Map')) {
691 return null;
692 }
693 else if (type.name === 'Set' && !scope.hasBinding('Set')) {
694 return null;
695 }
696 return maybeInstanceOfAnnotation(getAnnotation(path), type, annotation.typeParameters ? annotation.typeParameters.params : []);
697 },
698 type ({path, type}): ?boolean {
699 return null;
700 },
701 };
702 }
703
704 function compareAnnotations (a: TypeAnnotation, b: TypeAnnotation): ?boolean {
705 if (a.type === 'TypeAnnotation') {
706 a = a.typeAnnotation;
707 }
708 if (b.type === 'TypeAnnotation') {
709 b = b.typeAnnotation;
710 }
711 switch (a.type) {
712 case 'StringTypeAnnotation':
713 return maybeStringAnnotation(b);
714 case 'StringLiteralTypeAnnotation':
715 return compareStringLiteralAnnotations(a, b);
716 case 'NumberTypeAnnotation':
717 return maybeNumberAnnotation(b);
718 case 'NumericLiteralTypeAnnotation':
719 return compareNumericLiteralAnnotations(a, b);
720 case 'BooleanTypeAnnotation':
721 return maybeBooleanAnnotation(b);
722 case 'BooleanLiteralTypeAnnotation':
723 return compareBooleanLiteralAnnotations(a, b);
724 case 'FunctionTypeAnnotation':
725 return maybeFunctionAnnotation(b);
726 case 'AnyTypeAnnotation':
727 return null;
728 case 'MixedTypeAnnotation':
729 return null;
730 case 'ObjectTypeAnnotation':
731 return compareObjectAnnotation(a, b);
732 case 'ArrayTypeAnnotation':
733 return compareArrayAnnotation(a, b);
734 case 'GenericTypeAnnotation':
735 return compareGenericAnnotation(a, b);
736 case 'TupleTypeAnnotation':
737 return compareTupleAnnotation(a, b);
738 case 'UnionTypeAnnotation':
739 return compareUnionAnnotation(a, b);
740 case 'IntersectionTypeAnnotation':
741 return compareIntersectionAnnotation(a, b);
742 case 'NullableTypeAnnotation':
743 return compareNullableAnnotation(a, b);
744 default:
745 return null;
746 }
747 }
748
749 function compareStringLiteralAnnotations (a: StringLiteralTypeAnnotation, b: TypeAnnotation): ?boolean {
750 if (b.type === 'StringLiteralTypeAnnotation') {
751 return a.value === b.value;
752 }
753 else {
754 return maybeStringAnnotation(b) === false ? false : null;
755 }
756 }
757
758 function compareBooleanLiteralAnnotations (a: BooleanLiteralTypeAnnotation, b: TypeAnnotation): ?boolean {
759 if (b.type === 'BooleanLiteralTypeAnnotation') {
760 return a.value === b.value;
761 }
762 else {
763 return maybeBooleanAnnotation(b) === false ? false : null;
764 }
765 }
766
767 function compareNumericLiteralAnnotations (a: NumericLiteralTypeAnnotation, b: TypeAnnotation): ?boolean {
768 if (b.type === 'NumericLiteralTypeAnnotation') {
769 return a.value === b.value;
770 }
771 else {
772 return maybeNumberAnnotation(b) === false ? false : null;
773 }
774 }
775
776 function unionComparer (a: TypeAnnotation, b: TypeAnnotation, comparator: (a:TypeAnnotation, b:TypeAnnotation) => ?boolean): ?boolean {
777 if (!a.types || a.types.length === 0) {
778 return null;
779 }
780 let falseCount = 0;
781 let trueCount = 0;
782 if (!a.types) {
783 return null;
784 }
785 for (let type of a.types) {
786 const result = comparator(type, b);
787 if (result === true) {
788 if (b.type !== 'UnionTypeAnnotation') {
789 return true;
790 }
791 trueCount++;
792 }
793 else if (result === false) {
794 if (b.type === 'UnionTypeAnnotation') {
795 return false;
796 }
797 falseCount++;
798 }
799 }
800 if (falseCount === a.types.length) {
801 return false;
802 }
803 else if (trueCount === a.types.length) {
804 return true;
805 }
806 else {
807 return null;
808 }
809 }
810
811 function intersectionComparer (a: TypeAnnotation, b: TypeAnnotation, comparator: (a:TypeAnnotation, b:TypeAnnotation) => ?boolean): ?boolean {
812 let falseCount = 0;
813 let trueCount = 0;
814 if (!a.types) {
815 return null;
816 }
817 for (let type of a.types) {
818 const result = comparator(type, b);
819 if (result === true) {
820 trueCount++;
821 }
822 else if (result === false) {
823 return false;
824 }
825 }
826 if (trueCount === a.types.length) {
827 return true;
828 }
829 else {
830 return null;
831 }
832 }
833
834 function compareObjectAnnotation (a: Node, b: Node): ?boolean {
835 switch (b.type) {
836 case 'ObjectTypeAnnotation':
837 break;
838 case 'TypeAnnotation':
839 case 'FunctionTypeParam':
840 case 'NullableTypeAnnotation':
841 return compareObjectAnnotation(a, b.typeAnnotation);
842 case 'UnionTypeAnnotation':
843 return unionComparer(a, b, compareObjectAnnotation);
844 case 'IntersectionTypeAnnotation':
845 return intersectionComparer(a, b, compareObjectAnnotation);
846 case 'VoidTypeAnnotation':
847 case 'BooleanTypeAnnotation':
848 case 'BooleanLiteralTypeAnnotation':
849 case 'StringTypeAnnotation':
850 case 'StringLiteralTypeAnnotation':
851 case 'NumberTypeAnnotation':
852 case 'NumericLiteralTypeAnnotation':
853 case 'FunctionTypeAnnotation':
854 return false;
855 default:
856 return null;
857 }
858
859 // We're comparing two object annotations.
860 let allTrue = true;
861 for (let aprop of a.properties) {
862 let found = false;
863 for (let bprop of b.properties) {
864 if (bprop.key.name === aprop.key.name) {
865 const result = compareAnnotations(aprop.value, bprop.value);
866 if (result === false && !(aprop.optional && (bprop.optional || maybeNullableAnnotation(bprop.value) === true))) {
867 return false;
868 }
869 else {
870 found = result;
871 }
872 break;
873 }
874 }
875 if (found === false && !aprop.optional) {
876 return false;
877 }
878 allTrue = allTrue && found === true;
879 }
880 return allTrue ? true : null;
881 }
882
883 function compareArrayAnnotation (a: Node, b: Node): ?boolean {
884 switch (b.type) {
885 case 'TypeAnnotation':
886 case 'FunctionTypeParam':
887 case 'NullableTypeAnnotation':
888 return compareArrayAnnotation(a, b.typeAnnotation);
889 case 'UnionTypeAnnotation':
890 return unionComparer(a, b, compareArrayAnnotation);
891 case 'IntersectionTypeAnnotation':
892 return intersectionComparer(a, b, compareArrayAnnotation);
893 case 'VoidTypeAnnotation':
894 case 'BooleanTypeAnnotation':
895 case 'BooleanLiteralTypeAnnotation':
896 case 'StringTypeAnnotation':
897 case 'StringLiteralTypeAnnotation':
898 case 'NumberTypeAnnotation':
899 case 'NumericLiteralTypeAnnotation':
900 case 'FunctionTypeAnnotation':
901 return false;
902 default:
903 return null;
904 }
905 }
906
907 function compareGenericAnnotation (a: Node, b: Node): ?boolean {
908 switch (b.type) {
909 case 'TypeAnnotation':
910 case 'FunctionTypeParam':
911 case 'NullableTypeAnnotation':
912 return compareGenericAnnotation(a, b.typeAnnotation);
913 case 'GenericTypeAnnotation':
914 if (b.id.name === a.id.name) {
915 return true;
916 }
917 else {
918 return null;
919 }
920 case 'UnionTypeAnnotation':
921 return unionComparer(a, b, compareGenericAnnotation);
922 case 'IntersectionTypeAnnotation':
923 return intersectionComparer(a, b, compareGenericAnnotation);
924 default:
925 return null;
926 }
927 }
928
929 function compareTupleAnnotation (a: Node, b: Node): ?boolean {
930 if (b.type === 'TupleTypeAnnotation') {
931 if (b.types.length === 0) {
932 return null;
933 }
934 else if (b.types.length < a.types.length) {
935 return false;
936 }
937 return a.types.every((type, index) => compareAnnotations(type, b.types[index]));
938 }
939 switch (b.type) {
940 case 'TypeAnnotation':
941 case 'FunctionTypeParam':
942 case 'NullableTypeAnnotation':
943 return compareTupleAnnotation(a, b.typeAnnotation);
944 case 'UnionTypeAnnotation':
945 return unionComparer(a, b, compareTupleAnnotation);
946 case 'IntersectionTypeAnnotation':
947 return intersectionComparer(a, b, compareTupleAnnotation);
948 case 'VoidTypeAnnotation':
949 case 'BooleanTypeAnnotation':
950 case 'BooleanLiteralTypeAnnotation':
951 case 'StringTypeAnnotation':
952 case 'StringLiteralTypeAnnotation':
953 case 'NumberTypeAnnotation':
954 case 'NumericLiteralTypeAnnotation':
955 case 'FunctionTypeAnnotation':
956 return false;
957 default:
958 return null;
959 }
960 }
961
962 function compareUnionAnnotation (a: Node, b: Node): ?boolean {
963 switch (b.type) {
964 case 'NullableTypeAnnotation':
965 return compareUnionAnnotation(a, b.typeAnnotation);
966 case 'AnyTypeAnnotation':
967 case 'MixedTypeAnnotation':
968 return null;
969 default:
970 return unionComparer(a, b, compareAnnotations);
971 }
972 }
973
974 function compareNullableAnnotation (a: Node, b: Node): ?boolean {
975 switch (b.type) {
976 case 'TypeAnnotation':
977 case 'FunctionTypeParam':
978 return compareNullableAnnotation(a, b.typeAnnotation);
979 case 'NullableTypeAnnotation':
980 case 'VoidTypeAnnotation':
981 return null;
982 }
983 if (compareAnnotations(a.typeAnnotation, b) === true) {
984 return true;
985 }
986 else {
987 return null;
988 }
989 }
990
991 function arrayExpressionToTupleAnnotation (path: NodePath): TypeAnnotation {
992 const elements = path.get('elements');
993 return t.tupleTypeAnnotation(elements.map(element => getAnnotation(element)));
994 }
995
996 function checkNullable ({input, type, scope}): ?Node {
997 const check = checkAnnotation(input, type, scope);
998 if (!check) {
999 return;
1000 }
1001 return t.logicalExpression(
1002 "||",
1003 checks.void({input}),
1004 check
1005 );
1006 }
1007
1008 function checkTypeof ({input, annotation, scope}): ?Node {
1009 switch (annotation.type) {
1010 case 'GenericTypeAnnotation':
1011 const {id} = annotation;
1012 const path = Object.assign({}, input, {type: id.type, node: id, scope});
1013 return checkAnnotation(input, getAnnotation(path), scope);
1014 default:
1015 return checkAnnotation(input, annotation, scope);
1016 }
1017 }
1018
1019 function checkStringLiteral ({input, annotation}): ?Node {
1020 return checkEquals({input, expected: t.stringLiteral(annotation.value)});
1021 }
1022
1023 function checkNumericLiteral ({input, annotation}): ?Node {
1024 return checkEquals({input, expected: t.numericLiteral(annotation.value)});
1025 }
1026
1027 function checkBooleanLiteral ({input, annotation}): ?Node {
1028 return checkEquals({input, expected: t.booleanLiteral(annotation.value)});
1029 }
1030
1031 function checkUnion ({input, types, scope}): ?Node {
1032 const checks = types.map(type => checkAnnotation(input, type, scope)).filter(identity);
1033 return checks.reduce((last, check, index) => {
1034 if (last == null) {
1035 return check;
1036 }
1037 return t.logicalExpression(
1038 "||",
1039 last,
1040 check
1041 );
1042 }, null);
1043 }
1044
1045
1046 function checkIntersection ({input, types, scope}): ?Node {
1047 const checks = types.map(type => checkAnnotation(input, type, scope)).filter(identity);
1048 return checks.reduce((last, check, index) => {
1049 if (last == null) {
1050 return check;
1051 }
1052 return t.logicalExpression(
1053 "&&",
1054 last,
1055 check
1056 );
1057 }, null);
1058 }
1059
1060
1061 function checkMap ({input, types, scope}): Node {
1062 const [keyType, valueType] = types;
1063 const key = t.identifier('key');
1064 const value = t.identifier('value');
1065 const keyCheck = keyType ? checkAnnotation(key, keyType, scope) : null;
1066 const valueCheck = valueType ? checkAnnotation(value, valueType, scope) : null;
1067 if (!keyCheck) {
1068 if (!valueCheck) {
1069 return checkIsMap({input});
1070 }
1071 else {
1072 return checkMapValues({input, value, valueCheck});
1073 }
1074 }
1075 else {
1076 if (!valueCheck) {
1077 return checkMapKeys({input, key, keyCheck});
1078 }
1079 else {
1080 return checkMapEntries({input, key, value, keyCheck, valueCheck});
1081 }
1082 }
1083 }
1084
1085 function checkSet ({input, types, scope}): Node {
1086 const [valueType] = types;
1087 const value = t.identifier('value');
1088 const valueCheck = valueType ? checkAnnotation(value, valueType, scope) : null;
1089 if (!valueCheck) {
1090 return checkIsSet({input});
1091 }
1092 else {
1093 return checkSetEntries({input, value, valueCheck});
1094 }
1095 }
1096
1097 function checkGenerator ({input, types, scope}): Node {
1098 return checkIsGenerator({input});
1099 }
1100
1101 function checkIterable ({input, types, scope}): Node {
1102 return checkIsIterable({input});
1103 }
1104
1105 function checkArray ({input, types, scope}): Node {
1106 if (!types || types.length === 0) {
1107 return checkIsArray({input});
1108 }
1109 else if (types.length === 1) {
1110 const item = t.identifier('item');
1111 const type = types[0];
1112 const check = checkAnnotation(item, type, scope);
1113 if (!check) {
1114 return checkIsArray({input});
1115 }
1116 return t.logicalExpression(
1117 '&&',
1118 checkIsArray({input}),
1119 t.callExpression(
1120 t.memberExpression(input, t.identifier('every')),
1121 [t.functionExpression(null, [item], t.blockStatement([
1122 t.returnStatement(check)
1123 ]))]
1124 )
1125 );
1126 }
1127 else {
1128 // This is a tuple
1129 const checks = types.map(
1130 (type, index) => checkAnnotation(
1131 t.memberExpression(
1132 input,
1133 t.numericLiteral(index),
1134 true
1135 ),
1136 type,
1137 scope
1138 )
1139 ).filter(identity);
1140
1141 const checkLength = t.binaryExpression(
1142 '>=',
1143 t.memberExpression(
1144 input,
1145 t.identifier('length')
1146 ),
1147 t.numericLiteral(types.length)
1148 );
1149
1150 return checks.reduce((last, check, index) => {
1151 return t.logicalExpression(
1152 "&&",
1153 last,
1154 check
1155 );
1156 }, t.logicalExpression(
1157 '&&',
1158 checkIsArray({input}),
1159 checkLength
1160 ));
1161 }
1162 }
1163
1164 function checkTuple ({input, types, scope}): Node {
1165 if (types.length === 0) {
1166 return checkIsArray({input});
1167 }
1168
1169 // This is a tuple
1170 const checks = types.map(
1171 (type, index) => checkAnnotation(
1172 t.memberExpression(
1173 input,
1174 t.numericLiteral(index),
1175 true
1176 ),
1177 type,
1178 scope
1179 )
1180 ).filter(identity);
1181
1182 const checkLength = t.binaryExpression(
1183 '>=',
1184 t.memberExpression(
1185 input,
1186 t.identifier('length')
1187 ),
1188 t.numericLiteral(types.length)
1189 );
1190
1191 return checks.reduce((last, check, index) => {
1192 return t.logicalExpression(
1193 "&&",
1194 last,
1195 check
1196 );
1197 }, t.logicalExpression(
1198 '&&',
1199 checkIsArray({input}),
1200 checkLength
1201 ));
1202 }
1203
1204 function checkObject ({input, properties, scope}): Node {
1205 if (input.type === 'ObjectPattern') {
1206 return checkObjectPattern({input, properties, scope});
1207 }
1208 const check = properties.reduce((expr, prop, index) => {
1209 let target;
1210
1211 target = t.memberExpression(input, prop.key);
1212 let check = checkAnnotation(target, prop.value, scope);
1213 if (check) {
1214 if (prop.optional) {
1215 check = t.logicalExpression(
1216 '||',
1217 checks.undefined({input: target}),
1218 check
1219 );
1220 }
1221 return t.logicalExpression(
1222 "&&",
1223 expr,
1224 check
1225 );
1226 }
1227 else {
1228 return expr;
1229 }
1230 }, checkIsObject({input}));
1231
1232 return check;
1233 }
1234
1235 function checkObjectPattern ({input, properties, scope}): ?Node {
1236 const propNames = properties.reduce((names, prop) => {
1237 names[prop.key.name] = prop;
1238 return names;
1239 }, {});
1240 const propChecks = {};
1241 for (let item of input.properties) {
1242 let {key, value: id} = item;
1243 let prop = propNames[key.name];
1244 if (!prop) {
1245 continue;
1246 }
1247 const check = checkAnnotation(id, prop.value, scope);
1248 if (check) {
1249 propChecks[key.name] = check;
1250 }
1251 }
1252 return Object.keys(propChecks).reduce((last, name) => {
1253 const check = propChecks[name];
1254 if (last === null) {
1255 return check;
1256 }
1257 else {
1258 return t.logicalExpression('&&', last, check);
1259 }
1260 }, null);
1261 }
1262
1263 function createTypeAliasChecks (path: NodePath): Node {
1264 const {node, scope} = path;
1265 const {id, right: annotation} = node;
1266 const input = t.identifier('input');
1267 const check = checkAnnotation(input, annotation, scope) || t.booleanLiteral(true);
1268 const declaration = declareTypeChecker({id, check});
1269 declaration.isTypeChecker = true;
1270 declaration.savedTypeAnnotation = annotation;
1271 declaration.declarations[0].savedTypeAnnotation = annotation;
1272 return declaration;
1273 }
1274
1275
1276 function createInterfaceChecks (path: NodePath): Node {
1277 const {node, scope} = path;
1278 const {id, body: annotation} = node;
1279 const input = t.identifier('input');
1280 const check = node.extends.reduce(
1281 (check, extender) => {
1282 return t.logicalExpression(
1283 '&&',
1284 check,
1285 checkAnnotation(input, t.genericTypeAnnotation(extender.id), path.scope)
1286 );
1287 return check;
1288 },
1289 checkAnnotation(input, annotation, scope) || t.booleanLiteral(true)
1290 );
1291
1292 const declaration = declareTypeChecker({id, check});
1293 declaration.isTypeChecker = true;
1294 return declaration;
1295 }
1296
1297 function checkAnnotation (input: Node, annotation: TypeAnnotation, scope: Scope): ?Node {
1298 switch (annotation.type) {
1299 case 'TypeAnnotation':
1300 case 'FunctionTypeParam':
1301 return checkAnnotation(input, annotation.typeAnnotation, scope);
1302 case 'TypeofTypeAnnotation':
1303 return checks.typeof({input, annotation: annotation.argument, scope});
1304 case 'GenericTypeAnnotation':
1305 if (annotation.id.name === 'Array') {
1306 return checks.array({input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope});
1307 }
1308 else if (annotation.id.name === 'Generator' && !scope.hasBinding('Generator')) {
1309 return checks.generator({input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope});
1310 }
1311 else if (annotation.id.name === 'Iterable' && !scope.hasBinding('Iterable')) {
1312 return checks.iterable({input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope});
1313 }
1314 else if (annotation.id.name === 'Map' && !scope.hasBinding('Map')) {
1315 return checks.map({input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope});
1316 }
1317 else if (annotation.id.name === 'Set' && !scope.hasBinding('Set')) {
1318 return checks.set({input, types: annotation.typeParameters ? annotation.typeParameters.params : [], scope});
1319 }
1320 else if (annotation.id.name === 'Function') {
1321 return checks.function({input});
1322 }
1323 else if (annotation.id.name === 'Symbol') {
1324 return checks.symbol({input});
1325 }
1326 else if (isTypeChecker(annotation.id, scope)) {
1327 return checks.type({input, type: annotation.id});
1328 }
1329 else if (isPolymorphicType(annotation.id, scope)) {
1330 return;
1331 }
1332 else {
1333 return checks.instanceof({input, type: createTypeExpression(annotation.id)});
1334 }
1335 case 'TupleTypeAnnotation':
1336 return checks.tuple({input, types: annotation.types, scope});
1337 case 'NumberTypeAnnotation':
1338 return checks.number({input});
1339 case 'NumericLiteralTypeAnnotation':
1340 return checks.numericLiteral({input, annotation});
1341 case 'BooleanTypeAnnotation':
1342 return checks.boolean({input});
1343 case 'BooleanLiteralTypeAnnotation':
1344 return checks.booleanLiteral({input, annotation});
1345 case 'StringTypeAnnotation':
1346 return checks.string({input});
1347 case 'StringLiteralTypeAnnotation':
1348 return checks.stringLiteral({input, annotation});
1349 case 'UnionTypeAnnotation':
1350 return checks.union({input, types: annotation.types, scope});
1351 case 'IntersectionTypeAnnotation':
1352 return checks.intersection({input, types: annotation.types, scope});
1353 case 'ObjectTypeAnnotation':
1354 return checks.object({input, properties: annotation.properties || [], indexers: annotation.indexers, scope});
1355 case 'ArrayTypeAnnotation':
1356 return checks.array({input, types: [annotation.elementType || t.anyTypeAnnotation()], scope});
1357 case 'FunctionTypeAnnotation':
1358 return checks.function({input, params: annotation.params, returnType: annotation.returnType});
1359 case 'MixedTypeAnnotation':
1360 return checks.mixed({input});
1361 case 'AnyTypeAnnotation':
1362 case 'ExistentialTypeParam':
1363 return checks.any({input});
1364 case 'NullableTypeAnnotation':
1365 return checks.nullable({input, type: annotation.typeAnnotation, scope});
1366 case 'VoidTypeAnnotation':
1367 return checks.void({input});
1368 }
1369 }
1370
1371 function staticCheckAnnotation (path: NodePath, annotation: TypeAnnotation): ?boolean {
1372 const other = getAnnotation(path);
1373 switch (annotation.type) {
1374 case 'TypeAnnotation':
1375 case 'FunctionTypeParam':
1376 return staticCheckAnnotation(path, annotation.typeAnnotation);
1377 case 'GenericTypeAnnotation':
1378 if (isTypeChecker(annotation.id, path.scope)) {
1379 return staticChecks.type({path, type: annotation.id});
1380 }
1381 else if (isPolymorphicType(annotation.id, path.scope)) {
1382 return;
1383 }
1384 else if (annotation.id.name === 'Symbol') {
1385 return staticChecks.symbol(path);
1386 }
1387 else {
1388 return staticChecks.instanceof({path, annotation});
1389 }
1390 }
1391 return compareAnnotations(annotation, other);
1392 }
1393
1394 /**
1395 * Get the type annotation for a given node.
1396 */
1397 function getAnnotation (path: NodePath): TypeAnnotation {
1398 let annotation;
1399 try {
1400 annotation = getAnnotationShallow(path);
1401 }
1402 catch (e) {
1403 if (e instanceof SyntaxError) {
1404 throw e;
1405 }
1406 if (process.env.TYPECHECK_DEBUG) {
1407 console.error(e.stack);
1408 }
1409 }
1410 while (annotation && annotation.type === 'TypeAnnotation') {
1411 annotation = annotation.typeAnnotation;
1412 }
1413 return annotation || t.anyTypeAnnotation();
1414 }
1415
1416 function getAnnotationShallow (path: NodePath): ?TypeAnnotation {
1417 if (!path || !path.node) {
1418 return t.voidTypeAnnotation();
1419 }
1420 const {node, scope} = path;
1421
1422 if (node.type === 'TypeAlias') {
1423 return node.right;
1424 }
1425 else if (node.type === 'ClassProperty' && node.typeAnnotation) {
1426 return getClassPropertyAnnotation(path);
1427 }
1428 else if (node.type === 'ClassMethod' && node.returnType) {
1429 return getClassMethodAnnotation(path);
1430 }
1431 else if (node.type === 'ObjectProperty' && node.typeAnnotation) {
1432 return getObjectPropertyAnnotation(path);
1433 }
1434 else if (node.type === 'ObjectMethod' && node.returnType) {
1435 return getObjectMethodAnnotation(path);
1436 }
1437 else if (!node.typeAnnotation && !node.savedTypeAnnotation && !node.returnType) {
1438 switch (path.type) {
1439 case 'Identifier':
1440 const binding = scope.getBinding(node.name);
1441 if (!binding || !binding.identifier) {
1442 return path.getTypeAnnotation();
1443 }
1444 const id = binding.identifier;
1445 if (binding.path.type === 'ObjectPattern') {
1446 return getObjectPatternAnnotation(binding.path, node.name);
1447 }
1448 if (id.savedTypeAnnotation) {
1449 return id.savedTypeAnnotation;
1450 }
1451 else if (id.returnType) {
1452 return id.returnType;
1453 }
1454 else if (id.typeAnnotation) {
1455 return id.typeAnnotation;
1456 }
1457 else if (isPolymorphicType(id, scope)) {
1458 return t.anyTypeAnnotation();
1459 }
1460 return binding.constant ? binding.path.getTypeAnnotation() : path.getTypeAnnotation();
1461 case 'StringLiteral':
1462 case 'NumericLiteral':
1463 case 'BooleanLiteral':
1464 return createLiteralTypeAnnotation(path);
1465 case 'CallExpression':
1466 const callee = path.get('callee');
1467 if (callee.type === 'Identifier') {
1468 if (callee.name === 'Symbol') {
1469 return t.genericTypeAnnotation('Symbol');
1470 }
1471 const fn = getFunctionForIdentifier(callee);
1472 if (fn) {
1473 return getAnnotation(fn);
1474 }
1475 }
1476 break;
1477 case 'ThisExpression':
1478 return getThisExpressionAnnotation(path);
1479 case 'AssignmentExpression':
1480 return getAssignmentExpressionAnnotation(path);
1481 case 'MemberExpression':
1482 return getMemberExpressionAnnotation(path);
1483 case 'ArrayExpression':
1484 return getArrayExpressionAnnotation(path);
1485 case 'ObjectExpression':
1486 return getObjectExpressionAnnotation(path);
1487 case 'BinaryExpression':
1488 return getBinaryExpressionAnnotation(path);
1489 case 'BinaryExpression':
1490 return getBinaryExpressionAnnotation(path);
1491 case 'LogicalExpression':
1492 return getLogicalExpressionAnnotation(path);
1493 case 'ConditionalExpression':
1494 return getConditionalExpressionAnnotation(path);
1495 case 'ObjectMethod':
1496 return getObjectMethodAnnotation(path);
1497 case 'ObjectProperty':
1498 return getObjectPropertyAnnotation(path);
1499 case 'ClassDeclaration':
1500 return getClassDeclarationAnnotation(path);
1501 case 'ClassMethod':
1502 return getClassMethodAnnotation(path);
1503 case 'ClassProperty':
1504 return getClassPropertyAnnotation(path);
1505 default:
1506 return path.getTypeAnnotation();
1507
1508 }
1509 }
1510 return node.savedTypeAnnotation || node.returnType || node.typeAnnotation || path.getTypeAnnotation();
1511 }
1512
1513 function createLiteralTypeAnnotation (path: NodePath): ?TypeAnnotation {
1514 let annotation;
1515 if (path.isStringLiteral()) {
1516 annotation = t.stringLiteralTypeAnnotation();
1517 }
1518 else if (path.isNumericLiteral()) {
1519 annotation = t.numericLiteralTypeAnnotation();
1520 }
1521 else if (path.isBooleanLiteral()) {
1522 annotation = t.booleanLiteralTypeAnnotation();
1523 }
1524 else {
1525 return path.getTypeAnnotation();
1526 }
1527 annotation.value = path.node.value;
1528 return annotation;
1529 }
1530
1531 function getObjectPatternAnnotation (path: NodePath, name: string): ?TypeAnnotation {
1532 let annotation = keyByName(getAnnotation(path), name);
1533 let found;
1534 if (!path.node.properties) {
1535 return;
1536 }
1537 for (let prop of path.get('properties')) {
1538 if (prop.node.value && prop.node.value.name === name) {
1539 found = prop.get('key');
1540 break;
1541 }
1542 else if (prop.node.key.type === 'Identifier' && prop.node.key.name === name) {
1543 found = prop.get('key');
1544 break;
1545 }
1546 }
1547 if (!annotation || !found) {
1548 return;
1549 }
1550 if (found.type === 'Identifier') {
1551 annotation.value.authoritative = false;
1552 return annotation.value;
1553 }
1554 }
1555
1556
1557 function keyByName (node: Node, name: string): ?Node {
1558 if (!node.properties) {
1559 return;
1560 }
1561 for (let prop of node.properties) {
1562 if (prop.key && prop.key.name === name) {
1563 return prop;
1564 }
1565 }
1566 }
1567
1568 function valueByName (node: Node, name: string): ?Node {
1569 if (!node.properties) {
1570 return;
1571 }
1572 for (let prop of node.properties) {
1573 if (prop.value && prop.value.name === name) {
1574 return prop;
1575 }
1576 }
1577 }
1578
1579 function getObjectPropertyAnnotation (path: NodePath): ?TypeAnnotation {
1580 const {node} = path;
1581 let annotation = node.typeAnnotation || node.savedTypeAnnotation;
1582 if (!annotation) {
1583 if (node.value) {
1584 const value = path.get('value');
1585 if (value.isLiteral()) {
1586 annotation = createLiteralTypeAnnotation(value);
1587 }
1588 else {
1589 annotation = value.node.typeAnnotation || value.node.savedTypeAnnotation || t.anyTypeAnnotation();
1590 }
1591 }
1592 else {
1593 annotation = t.anyTypeAnnotation();
1594 }
1595 }
1596 return t.objectTypeProperty(
1597 node.key,
1598 annotation || t.anyTypeAnnotation()
1599 );
1600 }
1601
1602 function getObjectMethodAnnotation (path: NodePath): ?TypeAnnotation {
1603 const {node} = path;
1604 return t.objectTypeProperty(
1605 t.identifier(node.key.name),
1606 t.functionTypeAnnotation(
1607 null,
1608 node.params.map(param => param.savedTypeAnnotation || param.typeAnnotation),
1609 null,
1610 node.savedTypeAnnotation || node.returnType || node.typeAnnotation || t.anyTypeAnnotation()
1611 )
1612 );
1613 }
1614
1615 function getThisExpressionAnnotation (path: NodePath): ?TypeAnnotation {
1616 let parent = path.parentPath;
1617 loop: while (parent) {
1618 switch (parent.type) {
1619 case 'ClassDeclaration':
1620 return getAnnotation(parent);
1621 case 'ClassBody':
1622 return getAnnotation(parent.parentPath);
1623 case 'ClassMethod':
1624 case 'ClassProperty':
1625 return getAnnotation(parent.parentPath.parentPath);
1626 case 'ObjectProperty':
1627 return getAnnotation(parent.parentPath);
1628 case 'ObjectMethod':
1629 return getAnnotation(parent.parentPath);
1630 case 'FunctionExpression':
1631 if (parent.parentPath.type === 'ObjectProperty') {
1632 return getAnnotation(parent.parentPath.parentPath);
1633 }
1634 break loop;
1635 case 'ArrowFunctionExpression':
1636 parent = parent.parentPath;
1637 continue;
1638 }
1639 if (parent.isFunction()) {
1640 break;
1641 }
1642 parent = parent.parentPath;
1643 }
1644 return t.objectTypeAnnotation([]);
1645 }
1646
1647 function getClassDeclarationAnnotation (path: NodePath): ?TypeAnnotation {
1648 const body = path.get('body').get('body').map(getAnnotation).filter(annotation => annotation && annotation.type !== 'AnyTypeAnnotation');
1649 return t.objectTypeAnnotation(body);
1650 }
1651
1652 function getAssignmentExpressionAnnotation (path: NodePath): ?TypeAnnotation {
1653 if (path.node.operator === '=') {
1654 return getAnnotation(path.get('right'));
1655 }
1656 }
1657
1658 function getClassPropertyAnnotation (path: NodePath): ?TypeAnnotation {
1659 const {node} = path;
1660 if (node.computed) {
1661 return;
1662 }
1663 const annotation = node.typeAnnotation || (node.value ? node.value.savedTypeAnnotation || node.value.typeAnnotation : t.anyTypeAnnotation());
1664 return t.objectTypeProperty(
1665 node.key,
1666 annotation || t.anyTypeAnnotation()
1667 );
1668 }
1669
1670 function getClassMethodAnnotation (path: NodePath): ?TypeAnnotation {
1671 const {node} = path;
1672 if (node.computed) {
1673 return;
1674 }
1675 if (node.kind === 'get') {
1676 return t.objectTypeProperty(
1677 node.key,
1678 node.savedTypeAnnotation || node.returnType || node.typeAnnotation || t.anyTypeAnnotation()
1679 );
1680 }
1681 else if (node.kind === 'set') {
1682 return t.objectTypeProperty(
1683 node.key,
1684 node.params.map(param => param.savedTypeAnnotation || param.typeAnnotation).shift() || t.anyTypeAnnotation()
1685 );
1686 }
1687 else {
1688 return t.objectTypeProperty(
1689 node.key,
1690 t.functionTypeAnnotation(
1691 null,
1692 node.params.map(param => param.savedTypeAnnotation || param.typeAnnotation || t.anyTypeAnnotation()),
1693 null,
1694 node.savedTypeAnnotation || node.returnType || node.typeAnnotation || t.anyTypeAnnotation()
1695 )
1696 );
1697 }
1698 }
1699
1700 function getBinaryExpressionAnnotation (path: NodePath): TypeAnnotation {
1701 const {node} = path;
1702 if (isBooleanExpression(node)) {
1703 return t.booleanTypeAnnotation();
1704 }
1705 else {
1706 return t.anyTypeAnnotation();
1707 }
1708 }
1709
1710 function getLogicalExpressionAnnotation (path: NodePath): TypeAnnotation {
1711 const {node} = path;
1712 if (isBooleanExpression(node)) {
1713 return t.booleanTypeAnnotation();
1714 }
1715 else {
1716 let left = path.get('left');
1717 let right = path.get('right');
1718 switch (node.operator) {
1719 case '&&':
1720 case '||':
1721 ([left, right] = [getAnnotation(left), getAnnotation(right)]);
1722 if (t.isUnionTypeAnnotation(left)) {
1723 if (t.isUnionTypeAnnotation(right)) {
1724 return t.unionTypeAnnotation(left.types.concat(right.types));
1725 }
1726 else {
1727 return t.unionTypeAnnotation(left.types.concat(right));
1728 }
1729 }
1730 else {
1731 return t.unionTypeAnnotation([left, right]);
1732 }
1733 }
1734 return t.anyTypeAnnotation();
1735 }
1736 }
1737
1738
1739 function getConditionalExpressionAnnotation (path: NodePath): TypeAnnotation {
1740 const {node} = path;
1741 const consequent = getAnnotation(path.get('consequent'));
1742 const alternate = getAnnotation(path.get('alternate'));
1743 if (t.isUnionTypeAnnotation(consequent)) {
1744 if (t.isUnionTypeAnnotation(alternate)) {
1745 return t.unionTypeAnnotation(consequent.types.concat(alternate.types));
1746 }
1747 else {
1748 return t.unionTypeAnnotation(consequent.types.concat(alternate));
1749 }
1750 }
1751 else {
1752 return t.unionTypeAnnotation([consequent, alternate]);
1753 }
1754 }
1755
1756 function getArrayExpressionAnnotation (path: NodePath): TypeAnnotation {
1757 return t.genericTypeAnnotation(
1758 t.identifier('Array'),
1759 t.typeParameterDeclaration(path.get('elements').map(getAnnotation))
1760 );
1761 }
1762
1763 function getObjectExpressionAnnotation (path: NodePath): TypeAnnotation {
1764 const annotation = t.objectTypeAnnotation(
1765 path.get('properties').map(property => {
1766 if (property.computed) {
1767 return;
1768 }
1769 else {
1770 return getAnnotation(property);
1771 }
1772 }).filter(identity)
1773 );
1774 return annotation;
1775 }
1776
1777 function getMemberExpressionAnnotation (path: NodePath): TypeAnnotation {
1778 if (path.node.computed) {
1779 return getComputedMemberExpressionAnnotation(path);
1780 }
1781 const stack = [];
1782 let target = path;
1783 while (target.isMemberExpression()) {
1784 stack.push(target);
1785 if (target.node.computed) {
1786 break;
1787 }
1788 target = target.get('object');
1789 }
1790 const objectAnnotation = stack.reduceRight((last, target) => {
1791 let annotation = last;
1792 if (annotation == null) {
1793 if (stack.length === 1) {
1794 annotation = getAnnotation(target.get('object'));
1795 }
1796 else {
1797 return getAnnotation(target);
1798 }
1799 }
1800
1801 switch (annotation.type) {
1802 case 'AnyTypeAnnotation':
1803 return annotation;
1804 case 'NullableTypeAnnotation':
1805 case 'TypeAnnotation':
1806 annotation = annotation.typeAnnotation;
1807 }
1808
1809 if (annotation.type === 'GenericTypeAnnotation') {
1810 const typeChecker = getTypeChecker(annotation.id, path.scope);
1811 if (typeChecker) {
1812 annotation = getAnnotation(typeChecker);
1813 }
1814 else {
1815 const binding = path.scope.getBinding(annotation.id.name);
1816 if (binding) {
1817 annotation = getAnnotation(binding.path);
1818 }
1819 }
1820 }
1821 switch (annotation.type) {
1822 case 'AnyTypeAnnotation':
1823 return annotation;
1824 case 'ObjectTypeAnnotation':
1825 const id = target.get('property').node;
1826 for (let {key, value} of annotation.properties || []) {
1827 if (key.name === id.name) {
1828 return value;
1829 }
1830 }
1831 }
1832 return t.anyTypeAnnotation();
1833 }, null);
1834
1835 return objectAnnotation || path.getTypeAnnotation();
1836 }
1837
1838 function getComputedMemberExpressionAnnotation (path: NodePath): TypeAnnotation {
1839 const object = path.get('object');
1840 const property = path.get('property');
1841 let objectAnnotation = getAnnotation(object);
1842 if (objectAnnotation.type === 'TypeAnnotation' || objectAnnotation.type === 'NullableTypeAnnotation') {
1843 objectAnnotation = objectAnnotation.typeAnnotation;
1844 }
1845 let propertyAnnotation = getAnnotation(property);
1846 if (propertyAnnotation.type === 'TypeAnnotation' || propertyAnnotation.type === 'NullableTypeAnnotation') {
1847 propertyAnnotation = propertyAnnotation.typeAnnotation;
1848 }
1849 const {confident, value} = property.evaluate();
1850 if (!confident) {
1851 return path.getTypeAnnotation();
1852 }
1853 switch (objectAnnotation.type) {
1854 case 'TupleTypeAnnotation':
1855 if (objectAnnotation.types.length === 0) {
1856 break;
1857 }
1858 else if (typeof value === 'number') {
1859 if (!objectAnnotation.types[value]) {
1860 throw path.buildCodeFrameError(`Invalid computed member expression for tuple: ` + humanReadableType(objectAnnotation));
1861 }
1862 return objectAnnotation.types[value];
1863 }
1864 else {
1865 throw path.buildCodeFrameError(`Invalid computed member expression for tuple: ` + humanReadableType(objectAnnotation));
1866 }
1867 break;
1868 }
1869 return path.getTypeAnnotation();
1870 }
1871
1872 function getFunctionForIdentifier (path: NodePath): boolean|Node {
1873 if (path.type !== 'Identifier') {
1874 return false;
1875 }
1876 const ref = path.scope.getBinding(path.node.name);
1877 if (!ref) {
1878 return false;
1879 }
1880 return t.isFunction(ref.path.parent) && ref.path.parentPath;
1881 }
1882
1883 /**
1884 * Returns `true` if the annotation is definitely for an array,
1885 * otherwise `false`.
1886 */
1887 function isStrictlyArrayAnnotation (annotation: TypeAnnotation): boolean {
1888 switch (annotation.type) {
1889 case 'TypeAnnotation':
1890 case 'FunctionTypeParam':
1891 return isStrictlyArrayAnnotation(annotation.typeAnnotation);
1892 case 'GenericTypeAnnotation':
1893 return annotation.id.name === 'Array';
1894 case 'UnionTypeAnnotation':
1895 return annotation.types.every(isStrictlyArrayAnnotation);
1896 default:
1897 return false;
1898 }
1899 }
1900
1901 function compareMaybeUnion (annotation: TypeAnnotation, comparator: (node: TypeAnnotation) => ?boolean): ?boolean {
1902 let falseCount = 0;
1903 for (let type of annotation.types) {
1904 const result = comparator(type);
1905 if (result === true) {
1906 return true;
1907 }
1908 else if (result === false) {
1909 falseCount++;
1910 }
1911 }
1912 if (falseCount === annotation.types.length) {
1913 return false;
1914 }
1915 else {
1916 return null;
1917 }
1918 }
1919
1920 /**
1921 * Returns `true` if the annotation is compatible with a number,
1922 * `false` if it definitely isn't, or `null` if we're not sure.
1923 */
1924 function maybeNumberAnnotation (annotation: TypeAnnotation): ?boolean {
1925 switch (annotation.type) {
1926 case 'TypeAnnotation':
1927 case 'FunctionTypeParam':
1928 case 'NullableTypeAnnotation':
1929 return maybeNumberAnnotation(annotation.typeAnnotation);
1930 case 'NumberTypeAnnotation':
1931 case 'NumericLiteralTypeAnnotation':
1932 return true;
1933 case 'GenericTypeAnnotation':
1934 switch (annotation.id.name) {
1935 case 'Array':
1936 case 'Function':
1937 case 'Object':
1938 case 'String':
1939 case 'Boolean':
1940 case 'Date':
1941 case 'RegExp':
1942 return false;
1943 default:
1944 return null;
1945 }
1946 case 'UnionTypeAnnotation':
1947 return compareMaybeUnion(annotation, maybeNumberAnnotation);
1948 case 'AnyTypeAnnotation':
1949 case 'MixedTypeAnnotation':
1950 case 'IntersectionTypeAnnotation':
1951 return null;
1952 default:
1953 return false;
1954 }
1955 }
1956
1957 /**
1958 * Returns `true` if the annotation is compatible with a string,
1959 * `false` if it definitely isn't, or `null` if we're not sure.
1960 */
1961 function maybeStringAnnotation (annotation: TypeAnnotation): ?boolean {
1962 switch (annotation.type) {
1963 case 'TypeAnnotation':
1964 case 'FunctionTypeParam':
1965 case 'NullableTypeAnnotation':
1966 return maybeStringAnnotation(annotation.typeAnnotation);
1967 case 'StringTypeAnnotation':
1968 return true;
1969 case 'StringLiteralTypeAnnotation':
1970 return null;
1971 case 'GenericTypeAnnotation':
1972 switch (annotation.id.name) {
1973 case 'Array':
1974 case 'Function':
1975 case 'Object':
1976 case 'Number':
1977 case 'Boolean':
1978 case 'Date':
1979 case 'RegExp':
1980 return false;
1981 default:
1982 return null;
1983 }
1984 case 'UnionTypeAnnotation':
1985 let falseCount = 0;
1986 for (let type of annotation.types) {
1987 const result = maybeStringAnnotation(type);
1988 if (result === true) {
1989 return true;
1990 }
1991 else if (result === false) {
1992 falseCount++;
1993 }
1994 }
1995 if (falseCount === annotation.types.length) {
1996 return false;
1997 }
1998 else {
1999 return null;
2000 }
2001 case 'AnyTypeAnnotation':
2002 case 'MixedTypeAnnotation':
2003 case 'IntersectionTypeAnnotation':
2004 return null;
2005 default:
2006 return false;
2007 }
2008 }
2009
2010/**
2011 * Returns `true` if the annotation is compatible with a symbol,
2012 * `false` if it definitely isn't, or `null` if we're not sure.
2013 */
2014 function maybeSymbolAnnotation (annotation: TypeAnnotation): ?boolean {
2015 switch (annotation.type) {
2016 case 'TypeAnnotation':
2017 case 'FunctionTypeParam':
2018 case 'NullableTypeAnnotation':
2019 return maybeSymbolAnnotation(annotation.typeAnnotation);
2020 case 'GenericTypeAnnotation':
2021 switch (annotation.id.name) {
2022 case 'Array':
2023 case 'Function':
2024 case 'Object':
2025 case 'Number':
2026 case 'Boolean':
2027 case 'Date':
2028 case 'RegExp':
2029 return false;
2030 case 'Symbol':
2031 return true;
2032 default:
2033 return null;
2034 }
2035 case 'UnionTypeAnnotation':
2036 let falseCount = 0;
2037 for (let type of annotation.types) {
2038 const result = maybeSymbolAnnotation(type);
2039 if (result === true) {
2040 return true;
2041 }
2042 else if (result === false) {
2043 falseCount++;
2044 }
2045 }
2046 if (falseCount === annotation.types.length) {
2047 return false;
2048 }
2049 else {
2050 return null;
2051 }
2052 case 'AnyTypeAnnotation':
2053 case 'MixedTypeAnnotation':
2054 case 'IntersectionTypeAnnotation':
2055 return null;
2056 default:
2057 return false;
2058 }
2059 }
2060
2061
2062 /**
2063 * Returns `true` if the annotation is compatible with a boolean,
2064 * `false` if it definitely isn't, or `null` if we're not sure.
2065 */
2066 function maybeBooleanAnnotation (annotation: TypeAnnotation): ?boolean {
2067 switch (annotation.type) {
2068 case 'TypeAnnotation':
2069 case 'FunctionTypeParam':
2070 case 'NullableTypeAnnotation':
2071 return maybeBooleanAnnotation(annotation.typeAnnotation);
2072 case 'BooleanTypeAnnotation':
2073 case 'BooleanLiteralTypeAnnotation':
2074 return true;
2075 case 'GenericTypeAnnotation':
2076 switch (annotation.id.name) {
2077 case 'Array':
2078 case 'Function':
2079 case 'Object':
2080 case 'String':
2081 case 'Number':
2082 case 'Date':
2083 case 'RegExp':
2084 return false;
2085 default:
2086 return null;
2087 }
2088 case 'UnionTypeAnnotation':
2089 let falseCount = 0;
2090 for (let type of annotation.types) {
2091 const result = maybeBooleanAnnotation(type);
2092 if (result === true) {
2093 return true;
2094 }
2095 else if (result === false) {
2096 falseCount++;
2097 }
2098 }
2099 if (falseCount === annotation.types.length) {
2100 return false;
2101 }
2102 else {
2103 return null;
2104 }
2105 case 'AnyTypeAnnotation':
2106 case 'MixedTypeAnnotation':
2107 case 'IntersectionTypeAnnotation':
2108 return null;
2109 default:
2110 return false;
2111 }
2112 }
2113
2114
2115 /**
2116 * Returns `true` if the annotation is compatible with a function,
2117 * `false` if it definitely isn't, or `null` if we're not sure.
2118 */
2119 function maybeFunctionAnnotation (annotation: TypeAnnotation): ?boolean {
2120 switch (annotation.type) {
2121 case 'TypeAnnotation':
2122 case 'FunctionTypeParam':
2123 case 'NullableTypeAnnotation':
2124 return maybeFunctionAnnotation(annotation.typeAnnotation);
2125 case 'FunctionTypeAnnotation':
2126 return true;
2127 case 'GenericTypeAnnotation':
2128 switch (annotation.id.name) {
2129 case 'Array':
2130 case 'Number':
2131 case 'Object':
2132 case 'String':
2133 case 'Boolean':
2134 case 'Date':
2135 case 'RegExp':
2136 return false;
2137 default:
2138 return null;
2139 }
2140 case 'UnionTypeAnnotation':
2141 let falseCount = 0;
2142 for (let type of annotation.types) {
2143 const result = maybeFunctionAnnotation(type);
2144 if (result === true) {
2145 return true;
2146 }
2147 else if (result === false) {
2148 falseCount++;
2149 }
2150 }
2151 if (falseCount === annotation.types.length) {
2152 return false;
2153 }
2154 else {
2155 return null;
2156 }
2157 case 'AnyTypeAnnotation':
2158 case 'MixedTypeAnnotation':
2159 case 'IntersectionTypeAnnotation':
2160 return null;
2161 default:
2162 return false;
2163 }
2164 }
2165
2166 /**
2167 * Returns `true` if the annotation is compatible with an undefined or null type,
2168 * `false` if it definitely isn't, or `null` if we're not sure.
2169 */
2170 function maybeNullableAnnotation (annotation: TypeAnnotation): ?boolean {
2171 switch (annotation.type) {
2172 case 'NullableTypeAnnotation':
2173 case 'VoidTypeAnnotation':
2174 case 'MixedTypeAnnotation':
2175 return true;
2176 case 'TypeAnnotation':
2177 case 'FunctionTypeParam':
2178 return maybeNullableAnnotation(annotation.typeAnnotation);
2179 case 'GenericTypeAnnotation':
2180 switch (annotation.id.name) {
2181 case 'Array':
2182 case 'Number':
2183 case 'Object':
2184 case 'String':
2185 case 'Boolean':
2186 case 'Date':
2187 case 'RegExp':
2188 return false;
2189 case 'Generator':
2190 if (annotation.typeParameters && annotation.typeParameters.params.length > 1) {
2191 return maybeNullableAnnotation(annotation.typeParameters.params[1]);
2192 }
2193 else {
2194 return null;
2195 }
2196 default:
2197 return null;
2198 }
2199 case 'UnionTypeAnnotation':
2200 let falseCount = 0;
2201 for (let type of annotation.types) {
2202 const result = maybeNullableAnnotation(type);
2203 if (result === true) {
2204 return true;
2205 }
2206 else if (result === false) {
2207 falseCount++;
2208 }
2209 }
2210 if (falseCount === annotation.types.length) {
2211 return false;
2212 }
2213 else {
2214 return null;
2215 }
2216 default:
2217 return false;
2218 }
2219 }
2220
2221 /**
2222 * Returns `true` if the annotation is compatible with an object type,
2223 * `false` if it definitely isn't, or `null` if we're not sure.
2224 */
2225 function maybeInstanceOfAnnotation (annotation: TypeAnnotation, expected: Identifier, typeParameters: TypeAnnotation[]): ?boolean {
2226 switch (annotation.type) {
2227 case 'TypeAnnotation':
2228 case 'FunctionTypeParam':
2229 case 'NullableTypeAnnotation':
2230 return maybeInstanceOfAnnotation(annotation.typeAnnotation, expected, typeParameters);
2231 case 'GenericTypeAnnotation':
2232 if (annotation.id.name === expected.name) {
2233 if (typeParameters.length === 0) {
2234 return true;
2235 }
2236 if (annotation.typeParameters && annotation.typeParameters.params.length) {
2237 let trueCount = 0;
2238 let nullCount = 0;
2239 for (let i = 0; i < typeParameters.length && i < annotation.typeParameters.params.length; i++) {
2240 const result = compareAnnotations(typeParameters[i], annotation.typeParameters.params[i]);
2241 if (result === false) {
2242 return false;
2243 }
2244 else if (result === true) {
2245 trueCount++;
2246 }
2247 else {
2248 nullCount++;
2249 }
2250 }
2251 return trueCount > 0 && nullCount === 0 ? true : null;
2252 }
2253 }
2254 return null;
2255 case 'UnionTypeAnnotation':
2256 let falseCount = 0;
2257 for (let type of annotation.types) {
2258 const result = maybeInstanceOfAnnotation(type, expected, typeParameters);
2259 if (result === true) {
2260 return true;
2261 }
2262 else if (result === false) {
2263 falseCount++;
2264 }
2265 }
2266 if (falseCount === annotation.types.length) {
2267 return false;
2268 }
2269 else {
2270 return null;
2271 }
2272 case 'VoidTypeAnnotation':
2273 case 'BooleanTypeAnnotation':
2274 case 'BooleanLiteralTypeAnnotation':
2275 case 'StringTypeAnnotation':
2276 case 'StringLiteralTypeAnnotation':
2277 case 'NumberTypeAnnotation':
2278 case 'NumericLiteralTypeAnnotation':
2279 case 'FunctionTypeAnnotation':
2280 return false;
2281 default:
2282 return null;
2283 }
2284 }
2285
2286 /**
2287 * Returns `true` if the annotation is compatible with an array,
2288 * `false` if it definitely isn't, or `null` if we're not sure.
2289 */
2290 function maybeArrayAnnotation (annotation: TypeAnnotation): ?boolean {
2291 switch (annotation.type) {
2292 case 'TypeAnnotation':
2293 case 'FunctionTypeParam':
2294 case 'NullableTypeAnnotation':
2295 return maybeArrayAnnotation(annotation.typeAnnotation);
2296 case 'TupleTypeAnnotation':
2297 case 'ArrayTypeAnnotation':
2298 return true;
2299 case 'GenericTypeAnnotation':
2300 return annotation.id.name === 'Array' ? true : null;
2301 case 'UnionTypeAnnotation':
2302 let falseCount = 0;
2303 for (let type of annotation.types) {
2304 const result = maybeArrayAnnotation(type);
2305 if (result === true) {
2306 return true;
2307 }
2308 else if (result === false) {
2309 falseCount++;
2310 }
2311 }
2312 if (falseCount === annotation.types.length) {
2313 return false;
2314 }
2315 else {
2316 return null;
2317 }
2318 case 'AnyTypeAnnotation':
2319 case 'MixedTypeAnnotation':
2320 case 'IntersectionTypeAnnotation':
2321 return null;
2322 default:
2323 return false;
2324 }
2325 }
2326
2327 /**
2328 * Returns `true` if the annotation is compatible with an iterable,
2329 * `false` if it definitely isn't, or `null` if we're not sure.
2330 */
2331 function maybeIterableAnnotation (annotation: TypeAnnotation): ?boolean {
2332 switch (annotation.type) {
2333 case 'TypeAnnotation':
2334 case 'FunctionTypeParam':
2335 case 'NullableTypeAnnotation':
2336 return maybeIterableAnnotation(annotation.typeAnnotation);
2337 case 'TupleTypeAnnotation':
2338 case 'ArrayTypeAnnotation':
2339 return true;
2340 case 'GenericTypeAnnotation':
2341 return annotation.id.name === 'Iterable' ? true : null;
2342 case 'UnionTypeAnnotation':
2343 let falseCount = 0;
2344 for (let type of annotation.types) {
2345 const result = maybeIterableAnnotation(type);
2346 if (result === true) {
2347 return true;
2348 }
2349 else if (result === false) {
2350 falseCount++;
2351 }
2352 }
2353 if (falseCount === annotation.types.length) {
2354 return false;
2355 }
2356 else {
2357 return null;
2358 }
2359 case 'BooleanTypeAnnotation':
2360 case 'BooleanLiteralTypeAnnotation':
2361 case 'NumericLiteralTypeAnnotation':
2362 case 'NumberTypeAnnotation':
2363 case 'VoidTypeAnnotation':
2364 return false;
2365 default:
2366 return null;
2367 }
2368 }
2369
2370 /**
2371 * Returns `true` if the annotation is compatible with a tuple,
2372 * `false` if it definitely isn't, or `null` if we're not sure.
2373 */
2374 function maybeTupleAnnotation (annotation: TypeAnnotation): ?boolean {
2375 switch (annotation.type) {
2376 case 'TypeAnnotation':
2377 case 'FunctionTypeParam':
2378 case 'NullableTypeAnnotation':
2379 return maybeTupleAnnotation(annotation.typeAnnotation);
2380 case 'TupleTypeAnnotation':
2381 return true;
2382 case 'UnionTypeAnnotation':
2383 let falseCount = 0;
2384 for (let type of annotation.types) {
2385 const result = maybeTupleAnnotation(type);
2386 if (result === true) {
2387 return true;
2388 }
2389 else if (result === false) {
2390 falseCount++;
2391 }
2392 }
2393 if (falseCount === annotation.types.length) {
2394 return false;
2395 }
2396 else {
2397 return null;
2398 }
2399 case 'GenericTypeAnnotation':
2400 case 'AnyTypeAnnotation':
2401 case 'ArrayTypeAnnotation':
2402 case 'MixedTypeAnnotation':
2403 case 'IntersectionTypeAnnotation':
2404 return null;
2405 default:
2406 return false;
2407 }
2408 }
2409
2410 function humanReadableType (annotation: TypeAnnotation): string {
2411 switch (annotation.type) {
2412 case 'TypeAnnotation':
2413 case 'FunctionTypeParam':
2414 return humanReadableType(annotation.typeAnnotation);
2415
2416 case 'FunctionTypeAnnotation':
2417 // @fixme babel doesn't seem to like generating FunctionTypeAnnotations yet
2418 return `(${annotation.params.map(humanReadableType).join(', ')}) => ${humanReadableType(annotation.returnType)}`;
2419 default:
2420 return generate(annotation).code;
2421 }
2422 }
2423
2424 function getTypeChecker (id: Identifier|QualifiedTypeIdentifier, scope: Scope): NodePath|false {
2425 const binding = scope.getBinding(id.name);
2426 if (binding === undefined) {
2427 return false;
2428 }
2429 const {path} = binding;
2430 if (path == null) {
2431 return false;
2432 }
2433 else if (path.type === 'TypeAlias') {
2434 return path;
2435 }
2436 else if (path.type === 'VariableDeclaration' && path.node.isTypeChecker) {
2437 return path.get('declarations')[0];
2438 }
2439 else if (path.isImportSpecifier() && path.parent.importKind === 'type') {
2440 return path;
2441 }
2442 return false;
2443 }
2444
2445 function isTypeChecker (id: Identifier|QualifiedTypeIdentifier, scope: Scope): boolean {
2446 const binding = scope.getBinding(id.name);
2447 if (binding === undefined) {
2448 return false;
2449 }
2450 const {path} = binding;
2451 if (path == null) {
2452 return false;
2453 }
2454 else if (path.type === 'TypeAlias' || (path.type === 'VariableDeclaration' && path.node.isTypeChecker)) {
2455 return true;
2456 }
2457 else if (path.isImportSpecifier() && path.parent.importKind === 'type') {
2458 return true;
2459 }
2460 return false;
2461 }
2462
2463 function isPolymorphicType (id: Identifier|QualifiedTypeIdentifier, scope: Scope): boolean {
2464 const binding = scope.getBinding(id.name);
2465 if (binding !== undefined) {
2466 return false;
2467 }
2468 let {path} = scope;
2469 while (path && path.type !== 'Program') {
2470 const {node} = path;
2471 if ((t.isFunction(node) || t.isClass(node)) && node.typeParameters) {
2472 for (let param of node.typeParameters.params) {
2473 param.isPolymorphicType = true;
2474 if (param.name === id.name) {
2475 return true;
2476 }
2477 }
2478 }
2479 path = path.parentPath;
2480 }
2481 return false;
2482 }
2483
2484 function getPolymorphicType (id: Identifier|QualifiedTypeIdentifier, scope: Scope): ?Node {
2485 const binding = scope.getBinding(id.name);
2486 if (binding !== undefined) {
2487 return false;
2488 }
2489 let {path} = scope;
2490 while (path && path.type !== 'Program') {
2491 const {node} = path;
2492 if (t.isFunction(node) && node.typeParameters) {
2493 for (let param of node.typeParameters.params) {
2494 param.isPolymorphicType = true;
2495 if (param.name === id.name) {
2496 return param;
2497 }
2498 }
2499 }
2500 path = path.parent;
2501 }
2502 return null;
2503 }
2504
2505 function collectParamChecks (path: NodePath): Node[] {
2506 return path.get('params').map((param) => {
2507 const {node} = param;
2508 if (node.type === 'AssignmentPattern') {
2509 if (node.left.typeAnnotation) {
2510 return createDefaultParamGuard(param);
2511 }
2512 }
2513 else if (node.type === 'RestElement') {
2514 if (node.typeAnnotation) {
2515 return createRestParamGuard(param);
2516 }
2517 }
2518 else if (node.typeAnnotation) {
2519 return createParamGuard(param);
2520 }
2521 }).filter(identity);
2522 }
2523
2524 function createParamGuard (path: NodePath): ?Node {
2525 const {node, scope} = path;
2526
2527 node.hasBeenTypeChecked = true;
2528 node.savedTypeAnnotation = node.typeAnnotation;
2529 if (node.type === 'ObjectPattern') {
2530 return;
2531 }
2532 let check = checkAnnotation(node, node.typeAnnotation, scope);
2533 if (!check) {
2534 return;
2535 }
2536 if (node.optional) {
2537 check = t.logicalExpression(
2538 '||',
2539 checks.undefined({input: node}),
2540 check
2541 );
2542 }
2543 const message = paramTypeErrorMessage(node, scope);
2544 return guard({
2545 check,
2546 message
2547 });
2548 }
2549
2550 function createDefaultParamGuard (path: NodePath): ?Node {
2551 const {node, scope} = path;
2552 const {left: id, right: value} = node;
2553 const ok = staticCheckAnnotation(path.get('right'), id.typeAnnotation);
2554 if (ok === false) {
2555 throw path.buildCodeFrameError(`Invalid default value for argument "${id.name}", expected ${humanReadableType(id.typeAnnotation)}.`);
2556 }
2557 return createParamGuard(path.get('left'));
2558 }
2559
2560 function createRestParamGuard (path: NodePath): ?Node {
2561 const {node, scope} = path;
2562 const {argument: id} = node;
2563 id.hasBeenTypeChecked = true;
2564 node.savedTypeAnnotation = node.typeAnnotation;
2565 if (!isStrictlyArrayAnnotation(node.typeAnnotation)) {
2566 throw path.buildCodeFrameError(`Invalid type annotation for rest argument "${id.name}", expected an Array, got: ${humanReadableType(node.typeAnnotation)}.`);
2567 }
2568 let check = checkAnnotation(id, node.typeAnnotation, scope);
2569 if (!check) {
2570 return;
2571 }
2572 if (node.optional) {
2573 check = t.logicalExpression(
2574 '||',
2575 checks.undefined({input: id}),
2576 check
2577 );
2578 }
2579 const message = paramTypeErrorMessage(id, scope, node.typeAnnotation);
2580 return guard({
2581 check,
2582 message
2583 });
2584 }
2585
2586 function returnTypeErrorMessage (path: NodePath, fn: Node, id: ?Identifier|Literal): Node {
2587 const {node, scope} = path;
2588 const name = fn.id ? fn.id.name : '';
2589 let annotation = fn.returnType;
2590 if (annotation.type === 'TypeAnnotation') {
2591 annotation = annotation.typeAnnotation;
2592 }
2593 if (fn.generator && isGeneratorAnnotation(annotation) && annotation.typeParameters && annotation.typeParameters.params.length > 1) {
2594 annotation = annotation.typeParameters.params[1];
2595 }
2596 const message = `Function ${name ? `"${name}" ` : ''}return value violates contract, expected ${humanReadableType(annotation)} got `;
2597
2598 return t.binaryExpression(
2599 '+',
2600 t.stringLiteral(message),
2601 id ? readableName({input: id}) : node.argument ? readableName({input: node.argument}) : t.stringLiteral('undefined')
2602 );
2603 }
2604
2605 function yieldTypeErrorMessage (fn: Node, annotation: TypeAnnotation, id: Identifier|Literal): Node {
2606 const name = fn.id ? fn.id.name : '';
2607 const message = `Function ${name ? `"${name}" ` : ''} yielded an invalid value, expected ${humanReadableType(annotation)} got `;
2608
2609 return t.binaryExpression(
2610 '+',
2611 t.stringLiteral(message),
2612 readableName({input: id})
2613 );
2614 }
2615 function yieldNextTypeErrorMessage (fn: Node, annotation: TypeAnnotation, id: Identifier|Literal): Node {
2616 const name = fn.id ? fn.id.name : '';
2617 const message = `Generator ${name ? `"${name}" ` : ''}received an invalid next value, expected ${humanReadableType(annotation)} got `;
2618
2619 return t.binaryExpression(
2620 '+',
2621 t.stringLiteral(message),
2622 readableName({input: id})
2623 );
2624 }
2625
2626 function paramTypeErrorMessage (node: Node, scope: Scope, typeAnnotation: TypeAnnotation = node.typeAnnotation): Node {
2627 const name = node.name;
2628 const message = `Value of ${node.optional ? 'optional ' : ''}argument "${name}" violates contract, expected ${humanReadableType(typeAnnotation)} got `;
2629
2630 return t.binaryExpression(
2631 '+',
2632 t.stringLiteral(message),
2633 readableName({input: node})
2634 );
2635 }
2636
2637 function varTypeErrorMessage (node: Node, scope: Scope, annotation?: TypeAnnotation = node.typeAnnotation): Node {
2638 if (node.type === 'Identifier') {
2639 const name = node.name;
2640 const message = `Value of variable "${name}" violates contract, expected ${humanReadableType(annotation)} got `;
2641 return t.binaryExpression(
2642 '+',
2643 t.stringLiteral(message),
2644 readableName({input: node})
2645 );
2646 }
2647 else {
2648 const message = `Value of "${generate(node).code}" violates contract, expected ${humanReadableType(annotation)} got `;
2649 return t.binaryExpression(
2650 '+',
2651 t.stringLiteral(message),
2652 readableName({input: node})
2653 );
2654 }
2655 }
2656
2657 /**
2658 * Determine whether the given node can produce purely boolean results.
2659 */
2660 function isBooleanExpression (node: Node) {
2661 if (node.type === 'BinaryExpression' && BOOLEAN_BINARY_OPERATORS.indexOf(node.operator) > -1) {
2662 return true;
2663 }
2664 else if (node.type === 'LogicalExpression') {
2665 return isBooleanExpression(node.left) && isBooleanExpression(node.right);
2666 }
2667 else {
2668 return false;
2669 }
2670 }
2671
2672 /**
2673 * Convert type specifier to expression.
2674 */
2675 function createTypeExpression (node: Node) : Object {
2676 if (node.type == 'Identifier') {
2677 return node;
2678 }
2679 else if (node.type == 'QualifiedTypeIdentifier') {
2680 return t.memberExpression(
2681 createTypeExpression(node.qualification),
2682 createTypeExpression(node.id)
2683 );
2684 }
2685
2686 throw this.errorWithNode(`Unsupported type: ${node.type}`);
2687 }
2688
2689 /**
2690 * Get name of a type as a string.
2691 */
2692 function getTypeName (node: Node): string {
2693 if(node.type == 'Identifier') {
2694 return node.name
2695 }
2696 else if(node.type == 'QualifiedTypeIdentifier') {
2697 return getTypeName(node.qualification) + '.' + getTypeName(node.id);
2698 }
2699
2700 throw this.errorWithNode(`Unsupported type: ${node.type}`);
2701 }
2702
2703
2704 /**
2705 * Union two arrays.
2706 */
2707 function union (arr1: Array, arr2: Array): Array {
2708 for (let i = 0; i < arr2.length; i++) {
2709 let item = arr2[i];
2710 if (arr1.indexOf(item) === -1) {
2711 arr1.push(item);
2712 }
2713 }
2714 return arr1;
2715 }
2716
2717
2718 /**
2719 * Determine whether the given annotation allows any value.
2720 */
2721 function allowsAny (annotation: TypeAnnotation): boolean {
2722 if (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation') {
2723 return allowsAny(annotation.typeAnnotation);
2724 }
2725 else if (annotation.type === 'AnyTypeAnnotation' || annotation.type === 'MixedTypeAnnotation') {
2726 return true;
2727 }
2728 else if (annotation.type === 'UnionTypeAnnotation') {
2729 return annotation.types.some(allowsAny);
2730 }
2731 else {
2732 return false;
2733 }
2734 }
2735
2736 /**
2737 * Determine whether a given node is nully (null or undefined).
2738 */
2739 function isNodeNully (node: ?Node): boolean {
2740 if (node == null) {
2741 return true;
2742 }
2743 else if (node.type === 'Identifier' && node.name === 'undefined') {
2744 return true;
2745 }
2746 else if (node.type === 'Literal' && node.value === null) {
2747 return true;
2748 }
2749 else if (node.type === 'UnaryExpression' && node.operator === 'void') {
2750 return true;
2751 }
2752 else {
2753 return false;
2754 }
2755 }
2756
2757 /**
2758 * Determine whether the file should be skipped, based on the comments attached to the given node.
2759 */
2760 function maybeSkipFile (path: NodePath): boolean {
2761 if (path.node.leadingComments && path.node.leadingComments.length) {
2762 return path.node.leadingComments.some(comment => PRAGMA_IGNORE_FILE.test(comment.value));
2763 }
2764 return false;
2765 }
2766
2767 /**
2768 * Maybe skip the given path if it has a relevant pragma.
2769 */
2770 function maybeSkip (path: NodePath): boolean {
2771 const {node} = path;
2772 if (node.hasBeenTypeChecked) {
2773 return true;
2774 }
2775 if (node.leadingComments && node.leadingComments.length) {
2776 const comment = node.leadingComments[node.leadingComments.length - 1];
2777 if (PRAGMA_IGNORE_STATEMENT.test(comment.value)) {
2778 path.skip();
2779 return true;
2780 }
2781 }
2782 return false;
2783 }
2784
2785 /**
2786 * A function that returns its first argument, useful when filtering.
2787 */
2788 function identity <T> (input: T): T {
2789 return input;
2790 }
2791
2792 function getExpression (node: Node): Node {
2793 return t.isExpressionStatement(node) ? node.expression : node;
2794 }
2795
2796 function expression (input: string): Function {
2797 const fn: Function = template(input);
2798 return function (...args) {
2799 const node: Node = fn(...args);
2800 return getExpression(node);
2801 };
2802 }
2803}