300 lines
8.1 KiB
JavaScript
300 lines
8.1 KiB
JavaScript
// @flow
|
|
|
|
import wu from 'wu';
|
|
|
|
// @see flow#5376.
|
|
import type {
|
|
ArrayTypeAnnotation, ClassDeclaration, ClassProperty, Comment, FlowTypeAnnotation,
|
|
GenericTypeAnnotation, InterfaceDeclaration, IntersectionTypeAnnotation, TypeAlias,
|
|
UnionTypeAnnotation, NullableTypeAnnotation, ObjectTypeIndexer, ObjectTypeProperty,
|
|
StringLiteralTypeAnnotation, ObjectTypeAnnotation, AnyTypeAnnotation, MixedTypeAnnotation,
|
|
TupleTypeAnnotation, DeclareTypeAlias, DeclareInterface, DeclareClass,
|
|
} from '@babel/types';
|
|
|
|
import {
|
|
isIdentifier, isObjectTypeProperty, isStringLiteralTypeAnnotation, isClassProperty,
|
|
} from '@babel/types';
|
|
|
|
import Context from './context';
|
|
|
|
import type {
|
|
Type, RecordType, Field, ArrayType, TupleType, MapType, UnionType, IntersectionType,
|
|
MaybeType, NumberType, StringType, BooleanType, LiteralType, ReferenceType,
|
|
} from '../types';
|
|
|
|
import * as t from '../types';
|
|
|
|
import {extractPragmas} from './pragmas';
|
|
|
|
import {invariant} from '../utils';
|
|
|
|
function processTypeAlias(ctx: Context, node: TypeAlias | DeclareTypeAlias) {
|
|
const {name} = node.id;
|
|
const type = makeType(ctx, node.right);
|
|
|
|
// TODO: support function aliases.
|
|
invariant(type);
|
|
|
|
ctx.define(name, type);
|
|
}
|
|
|
|
// TODO: type params.
|
|
function processInterfaceDeclaration(
|
|
ctx: Context,
|
|
node: InterfaceDeclaration | DeclareInterface | DeclareClass
|
|
) {
|
|
const {name} = node.id;
|
|
const type = makeType(ctx, node.body);
|
|
|
|
invariant(type);
|
|
|
|
if (node.extends.length === 0) {
|
|
ctx.define(name, type);
|
|
return;
|
|
}
|
|
|
|
const parts = [];
|
|
|
|
for (const extend of node.extends) {
|
|
const {name} = extend.id;
|
|
const type = ctx.query(name);
|
|
|
|
invariant(type && type.id);
|
|
|
|
const reference = t.createReference(t.clone(type.id));
|
|
|
|
parts.push(reference);
|
|
}
|
|
|
|
parts.push(type);
|
|
|
|
const intersection = t.createIntersection(parts);
|
|
|
|
ctx.define(name, intersection);
|
|
}
|
|
|
|
// TODO: type params.
|
|
function processClassDeclaration(ctx: Context, node: ClassDeclaration) {
|
|
const props: $FlowFixMe = wu(node.body.body).filter(isClassProperty).toArray();
|
|
|
|
const {name} = node.id;
|
|
const type = makeRecord(ctx, props);
|
|
|
|
if (!node.superClass) {
|
|
ctx.define(name, type);
|
|
return;
|
|
}
|
|
|
|
// TODO: warning about expressions here.
|
|
invariant(isIdentifier(node.superClass));
|
|
|
|
const base = ctx.query(node.superClass.name);
|
|
|
|
invariant(base && base.id);
|
|
|
|
const baseRef = t.createReference(t.clone(base.id));
|
|
const intersection = t.createIntersection([baseRef, type]);
|
|
|
|
ctx.define(name, intersection);
|
|
}
|
|
|
|
function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
|
|
// TODO: ThisTypeAnnotation
|
|
// TODO: VoidTypeAnnotation
|
|
// TODO: TypeofTypeAnnotation
|
|
switch (node.type) {
|
|
case 'NullLiteralTypeAnnotation':
|
|
return t.createLiteral(null);
|
|
case 'BooleanTypeAnnotation':
|
|
return t.createBoolean();
|
|
case 'NumberTypeAnnotation':
|
|
return t.createNumber('f64');
|
|
case 'StringTypeAnnotation':
|
|
return t.createString();
|
|
case 'TypeAnnotation':
|
|
return makeType(ctx, node.typeAnnotation);
|
|
case 'NullableTypeAnnotation':
|
|
return makeMaybe(ctx, node);
|
|
case 'ObjectTypeAnnotation':
|
|
return makeComplex(ctx, node);
|
|
case 'ArrayTypeAnnotation':
|
|
return makeArray(ctx, node);
|
|
case 'TupleTypeAnnotation':
|
|
return makeTuple(ctx, node);
|
|
case 'UnionTypeAnnotation':
|
|
return makeUnion(ctx, node);
|
|
case 'IntersectionTypeAnnotation':
|
|
return makeIntersection(ctx, node);
|
|
case 'StringLiteralTypeAnnotation':
|
|
return t.createLiteral(node.value);
|
|
case 'GenericTypeAnnotation':
|
|
return makeReference(ctx, node);
|
|
case 'AnyTypeAnnotation':
|
|
return t.createAny();
|
|
case 'MixedTypeAnnotation':
|
|
return t.createMixed();
|
|
case 'FunctionTypeAnnotation':
|
|
return null;
|
|
default:
|
|
invariant(false, `Unknown node: ${node.type}`);
|
|
}
|
|
}
|
|
|
|
function makeMaybe(ctx: Context, node: NullableTypeAnnotation): ?MaybeType {
|
|
const type = makeType(ctx, node.typeAnnotation);
|
|
|
|
if (!type) {
|
|
return null;
|
|
}
|
|
|
|
return type.kind === 'maybe' ? type : t.createMaybe(type);
|
|
}
|
|
|
|
function makeComplex(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 t.createIntersection(parts);
|
|
}
|
|
|
|
function makeRecord<T: ObjectTypeProperty | ClassProperty>(ctx: Context, nodes: T[]): RecordType {
|
|
const fields = wu(nodes)
|
|
.map(node => makeField(ctx, node))
|
|
.filter()
|
|
.toArray();
|
|
|
|
return t.createRecord(fields);
|
|
}
|
|
|
|
function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Field {
|
|
if ((node: $FlowIssue<3129>).static) {
|
|
return null;
|
|
}
|
|
|
|
let type = null;
|
|
|
|
if (node.leadingComments) {
|
|
const pragma = (wu: $FlowIssue<4431>)(node.leadingComments)
|
|
.pluck('value')
|
|
.map(extractPragmas)
|
|
.flatten()
|
|
.find(pragma => pragma.kind === 'type');
|
|
|
|
if (pragma) {
|
|
type = pragma.value;
|
|
}
|
|
}
|
|
|
|
if (!type) {
|
|
const value = isObjectTypeProperty(node) ? node.value : node.typeAnnotation;
|
|
|
|
// TODO: no type annotation for the class property.
|
|
invariant(value);
|
|
|
|
type = makeType(ctx, value);
|
|
}
|
|
|
|
if (!type) {
|
|
return null;
|
|
}
|
|
|
|
// TODO: warning about computed properties.
|
|
|
|
invariant(isObjectTypeProperty(node) || !node.computed);
|
|
invariant(isIdentifier(node.key));
|
|
|
|
return {
|
|
name: node.key.name,
|
|
value: type,
|
|
required: node.optional == null || !node.optional,
|
|
};
|
|
}
|
|
|
|
function makeMap(ctx: Context, node: ObjectTypeIndexer): ?MapType {
|
|
const keys = makeType(ctx, node.key);
|
|
const values = makeType(ctx, node.value);
|
|
|
|
return keys && values ? t.createMap(keys, values) : null;
|
|
}
|
|
|
|
function makeArray(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType {
|
|
const items = makeType(ctx, node.elementType);
|
|
|
|
return items != null ? t.createArray(items) : null;
|
|
}
|
|
|
|
function makeTuple(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 t.createTuple(items);
|
|
}
|
|
|
|
function makeUnion(ctx: Context, node: UnionTypeAnnotation): ?Type {
|
|
const variants = wu(node.types)
|
|
.map(node => makeType(ctx, node))
|
|
.filter()
|
|
.toArray();
|
|
|
|
return variants.length === 0 ? null
|
|
: variants.length === 1 ? variants[0]
|
|
: t.createUnion(variants);
|
|
}
|
|
|
|
function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type {
|
|
// TODO: warning about nulls.
|
|
const parts = wu(node.types)
|
|
.map(node => makeType(ctx, node))
|
|
.filter()
|
|
.toArray();
|
|
|
|
return parts.length === 0 ? null
|
|
: parts.length === 1 ? parts[0]
|
|
: t.createIntersection(parts);
|
|
}
|
|
|
|
function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type {
|
|
const {name} = node.id;
|
|
const params = node.typeParameters
|
|
&& wu(node.typeParameters.params).map(n => makeType(ctx, n)).toArray();
|
|
|
|
const type = ctx.query(name, params);
|
|
|
|
if (!type) {
|
|
return null;
|
|
}
|
|
|
|
if (!type.id) {
|
|
return t.clone(type);
|
|
}
|
|
|
|
return t.createReference(t.clone(type.id));
|
|
}
|
|
|
|
export default {
|
|
TypeAlias: processTypeAlias,
|
|
DeclareTypeAlias: processTypeAlias,
|
|
InterfaceDeclaration: processInterfaceDeclaration,
|
|
DeclareInterface: processInterfaceDeclaration,
|
|
ClassDeclaration: processClassDeclaration,
|
|
DeclareClass: processInterfaceDeclaration,
|
|
}
|