flow2schema/src/definitions.js

338 lines
8.1 KiB
JavaScript
Raw Normal View History

2017-11-28 16:35:28 +03:00
import wu from 'wu';
// @see flow#5376.
2017-11-21 15:28:59 +03:00
import type {
ArrayTypeAnnotation, ClassDeclaration, ClassProperty, Comment, FlowTypeAnnotation,
GenericTypeAnnotation, InterfaceDeclaration, IntersectionTypeAnnotation, TypeAlias,
UnionTypeAnnotation, NullableTypeAnnotation, ObjectTypeIndexer, ObjectTypeProperty,
2017-11-28 18:19:01 +03:00
StringLiteralTypeAnnotation, ObjectTypeAnnotation, AnyTypeAnnotation, MixedTypeAnnotation,
2017-11-28 18:38:55 +03:00
TupleTypeAnnotation,
2017-11-21 15:28:59 +03:00
} from '@babel/types';
import {
isIdentifier, isObjectTypeProperty, isStringLiteralTypeAnnotation, isClassProperty,
} from '@babel/types';
import Context from './context';
import type {
2017-11-28 16:35:28 +03:00
Type, RecordType, Field, ArrayType, TupleType, MapType, UnionType, IntersectionType,
MaybeType, NumberType, StringType, BooleanType, LiteralType, ReferenceType,
} from './types';
import {extractPragmas} from './pragmas';
2017-11-21 15:28:59 +03:00
2017-11-28 16:35:28 +03:00
import {invariant} from './utils';
2017-11-21 15:28:59 +03:00
function processTypeAlias(ctx: Context, node: TypeAlias) {
2017-11-28 16:35:28 +03:00
const {name} = node.id;
const type = makeType(ctx, node.right);
2017-11-21 15:28:59 +03:00
// TODO: support function aliases.
2017-11-28 16:35:28 +03:00
invariant(type);
2017-11-21 15:28:59 +03:00
2017-11-28 16:35:28 +03:00
ctx.define(name, type);
2017-11-21 15:28:59 +03:00
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
// TODO: type params.
function processInterfaceDeclaration(ctx: Context, node: InterfaceDeclaration) {
2017-11-28 16:35:28 +03:00
const {name} = node.id;
const type = makeType(ctx, node.body);
2017-11-03 11:00:34 +03:00
2017-11-28 16:35:28 +03:00
invariant(type);
2017-11-03 11:00:34 +03:00
2017-11-28 16:35:28 +03:00
if (node.extends.length === 0) {
ctx.define(name, type);
return;
}
2017-11-03 11:00:34 +03:00
2017-11-28 16:35:28 +03:00
const parts = [];
2017-11-03 11:00:34 +03:00
2017-11-28 16:35:28 +03:00
for (const extend of node.extends) {
const {name} = extend.id;
const type = ctx.query(name);
2017-11-03 11:00:34 +03:00
2017-11-28 16:35:28 +03:00
invariant(type.id);
2017-11-02 16:28:53 +03:00
2017-11-28 16:35:28 +03:00
parts.push({
kind: 'reference',
2017-11-30 14:27:05 +03:00
to: type.id.slice(),
2017-11-28 16:35:28 +03:00
});
2017-11-21 15:28:59 +03:00
}
2017-11-04 22:13:08 +03:00
2017-11-28 16:35:28 +03:00
parts.push(type);
ctx.define(name, {
kind: 'intersection',
parts,
});
2017-11-21 15:28:59 +03:00
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
// TODO: type params.
function processClassDeclaration(ctx: Context, node: ClassDeclaration) {
2017-11-28 16:35:28 +03:00
const props: $FlowFixMe = wu(node.body.body).filter(isClassProperty).toArray();
2017-11-03 10:52:53 +03:00
2017-11-28 16:35:28 +03:00
const {name} = node.id;
const type = makeRecord(ctx, props);
2017-11-03 10:52:53 +03:00
2017-11-28 16:35:28 +03:00
if (!node.superClass) {
ctx.define(name, type);
return;
}
2017-11-03 10:52:53 +03:00
2017-11-28 16:35:28 +03:00
// TODO: warning about expressions here.
invariant(isIdentifier(node.superClass));
2017-11-02 16:28:53 +03:00
2017-11-28 16:35:28 +03:00
const base = ctx.query(node.superClass.name);
2017-11-04 22:13:08 +03:00
2017-11-28 16:35:28 +03:00
invariant(base.id);
const baseRef = {
kind: 'reference',
2017-11-30 14:27:05 +03:00
to: base.id.slice(),
2017-11-28 16:35:28 +03:00
};
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
ctx.define(name, {
kind: 'intersection',
parts: [baseRef, type],
});
2017-11-21 15:28:59 +03:00
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
switch (node.type) {
case 'NullLiteralTypeAnnotation':
2017-11-28 16:35:28 +03:00
return {kind: 'literal', value: null};
2017-11-21 15:28:59 +03:00
case 'BooleanTypeAnnotation':
2017-11-28 16:35:28 +03:00
return {kind: 'boolean'};
2017-11-21 15:28:59 +03:00
case 'NumberTypeAnnotation':
2017-11-28 16:35:28 +03:00
return {kind: 'number', repr: 'f64'};
2017-11-21 15:28:59 +03:00
case 'StringTypeAnnotation':
2017-11-28 16:35:28 +03:00
return {kind: 'string'};
2017-11-21 15:28:59 +03:00
case 'TypeAnnotation':
return makeType(ctx, node.typeAnnotation);
case 'NullableTypeAnnotation':
2017-11-28 16:35:28 +03:00
return makeMaybe(ctx, node);
2017-11-21 15:28:59 +03:00
case 'ObjectTypeAnnotation':
2017-11-28 16:35:28 +03:00
return makeComplexType(ctx, node);
2017-11-21 15:28:59 +03:00
case 'ArrayTypeAnnotation':
return makeArrayType(ctx, node);
2017-11-28 18:38:55 +03:00
case 'TupleTypeAnnotation':
return makeTupleType(ctx, node);
2017-11-21 15:28:59 +03:00
case 'UnionTypeAnnotation':
return makeUnionType(ctx, node);
case 'IntersectionTypeAnnotation':
return makeIntersection(ctx, node);
case 'StringLiteralTypeAnnotation':
2017-11-28 16:35:28 +03:00
return {kind: 'literal', value: node.value};
2017-11-21 15:28:59 +03:00
case 'GenericTypeAnnotation':
return makeReference(ctx, node);
2017-11-28 18:19:01 +03:00
case 'AnyTypeAnnotation':
return {kind: 'any'};
case 'MixedTypeAnnotation':
return {kind: 'mixed'};
2017-11-21 15:28:59 +03:00
case 'FunctionTypeAnnotation':
2017-10-29 01:55:39 +03:00
return null;
2017-11-21 15:28:59 +03:00
default:
2017-11-28 16:35:28 +03:00
invariant(false, `Unknown node: ${node.type}`);
2017-11-21 15:28:59 +03:00
}
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
function makeMaybe(ctx: Context, node: NullableTypeAnnotation): ?MaybeType {
2017-11-21 15:28:59 +03:00
const type = makeType(ctx, node.typeAnnotation);
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
if (!type) {
2017-10-29 01:55:39 +03:00
return null;
2017-11-21 15:28:59 +03:00
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
return {
kind: 'maybe',
value: type,
};
}
function makeComplexType(ctx: Context, node: ObjectTypeAnnotation): Type {
const maps = wu(node.indexers)
.map(node => makeMap(ctx, node))
.filter()
.toArray();
const record = makeRecord(ctx, node.properties);
if (maps.length === 1 && record.fields.length === 0) {
return maps[0];
}
if (maps.length === 0) {
return record;
}
const parts = record.fields.length > 0 ? [record, ...maps] : maps;
return {
kind: 'intersection',
parts,
};
2017-11-21 15:28:59 +03:00
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
function makeRecord<T: ObjectTypeProperty | ClassProperty>(ctx: Context, nodes: T[]): RecordType {
2017-11-28 16:35:28 +03:00
const fields = wu(nodes)
.map(node => makeField(ctx, node))
.filter()
.toArray();
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
return {
2017-11-28 16:35:28 +03:00
kind: 'record',
2017-11-21 15:28:59 +03:00
fields,
};
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Field {
if ((node: $FlowIssue<3129>).static) {
2017-11-21 15:28:59 +03:00
return null;
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
let type = null;
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
if (node.leadingComments) {
2017-11-28 16:35:28 +03:00
const pragma = (wu: $FlowIssue<4431>)(node.leadingComments)
.pluck('value')
.map(extractPragmas)
.flatten()
.find(pragma => pragma.kind === 'type');
if (pragma) {
type = pragma.value;
}
2017-11-21 15:28:59 +03:00
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
if (!type) {
2017-11-21 15:28:59 +03:00
const value = isObjectTypeProperty(node) ? node.value : node.typeAnnotation;
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
// TODO: no type annotation for the class property.
invariant(value);
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
type = makeType(ctx, value);
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
if (!type) {
2017-11-21 15:28:59 +03:00
return null;
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
// TODO: warning about computed properties.
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
invariant(isObjectTypeProperty(node) || !node.computed);
invariant(isIdentifier(node.key));
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
return {
name: node.key.name,
2017-11-28 16:35:28 +03:00
value: type,
required: node.optional == null || !node.optional,
2017-11-21 15:28:59 +03:00
};
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
function makeMap(ctx: Context, node: ObjectTypeIndexer): ?MapType {
const keys = makeType(ctx, node.key);
2017-11-21 15:28:59 +03:00
const values = makeType(ctx, node.value);
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
if (!(keys && values)) {
2017-11-21 15:28:59 +03:00
return null;
}
2017-10-29 01:55:39 +03:00
2017-11-21 15:28:59 +03:00
return {
2017-11-28 16:35:28 +03:00
kind: 'map',
keys,
2017-11-21 15:28:59 +03:00
values,
};
}
2017-11-03 10:34:36 +03:00
2017-11-21 15:28:59 +03:00
function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType {
const items = makeType(ctx, node.elementType);
2017-11-03 10:34:36 +03:00
2017-11-28 16:35:28 +03:00
if (!items) {
2017-11-21 15:28:59 +03:00
return null;
}
2017-11-03 10:34:36 +03:00
2017-11-21 15:28:59 +03:00
return {
2017-11-28 16:35:28 +03:00
kind: 'array',
2017-11-21 15:28:59 +03:00
items,
};
}
2017-11-28 18:38:55 +03:00
function makeTupleType(ctx: Context, node: TupleTypeAnnotation): ?TupleType {
// TODO: warning about nulls.
const items = wu(node.types).map(node => makeType(ctx, node)).toArray();
if (items.length === 0 || !wu(items).some()) {
return null;
}
return {
kind: 'tuple',
items,
};
}
2017-11-28 17:22:49 +03:00
function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?Type {
2017-11-28 16:35:28 +03:00
const variants = wu(node.types)
.map(node => makeType(ctx, node))
.filter()
.toArray();
2017-11-13 21:11:18 +03:00
2017-11-21 15:28:59 +03:00
if (variants.length === 0) {
return null;
}
2017-11-28 17:22:49 +03:00
if (variants.length === 1) {
return variants[0];
}
2017-11-06 02:13:41 +03:00
2017-11-28 16:35:28 +03:00
return {
kind: 'union',
variants,
};
2017-11-21 15:28:59 +03:00
}
2017-11-21 15:28:59 +03:00
function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type {
2017-11-28 16:35:28 +03:00
// TODO: warning about nulls.
const parts = wu(node.types)
.map(node => makeType(ctx, node))
.filter()
.toArray();
2017-11-03 11:00:34 +03:00
2017-11-28 16:35:28 +03:00
if (parts.length === 0) {
2017-11-21 15:28:59 +03:00
return null;
}
2017-10-29 01:55:39 +03:00
2017-11-28 16:35:28 +03:00
if (parts.length === 1) {
return parts[0];
}
2017-11-02 16:28:53 +03:00
2017-11-21 15:28:59 +03:00
return {
2017-11-28 16:35:28 +03:00
kind: 'intersection',
parts,
2017-11-21 15:28:59 +03:00
};
2017-11-02 16:28:53 +03:00
}
2017-11-28 16:35:28 +03:00
function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type {
2017-11-21 15:28:59 +03:00
const {name} = node.id;
2017-11-28 16:35:28 +03:00
const params = node.typeParameters
&& wu(node.typeParameters.params).map(n => makeType(ctx, n)).toArray();
2017-11-03 10:41:36 +03:00
2017-11-21 15:28:59 +03:00
const type = ctx.query(name, params);
2017-11-03 10:41:36 +03:00
2017-11-28 16:35:28 +03:00
if (!type.id) {
2017-11-21 15:28:59 +03:00
return type;
2017-11-03 10:41:36 +03:00
}
2017-11-28 16:35:28 +03:00
return {
kind: 'reference',
2017-11-30 14:27:05 +03:00
to: type.id.slice(),
2017-11-28 16:35:28 +03:00
};
2017-11-02 23:09:18 +03:00
}
2017-11-21 15:28:59 +03:00
export default {
TypeAlias: processTypeAlias,
InterfaceDeclaration: processInterfaceDeclaration,
ClassDeclaration: processClassDeclaration,
2017-11-03 10:34:36 +03:00
}