flow2schema/src/definitions.js

393 lines
8.6 KiB
JavaScript
Raw Normal View History

2017-11-18 12:38:34 +03:00
import * as t from '@babel/types';
2017-10-29 01:55:39 +03:00
import {define, query, namespace} from './commands';
2017-11-18 12:38:34 +03:00
import {invariant, partition} from './utils';
import type {Schema} from './schema';
type E = Generator<any, any, any>;
2017-10-29 01:55:39 +03:00
export default {
2017-11-09 12:10:21 +03:00
entries: [
'TypeAlias',
'InterfaceDeclaration',
'ClassDeclaration',
],
2017-11-06 14:29:26 +03:00
2017-11-18 12:38:34 +03:00
* TypeAlias(node: t.TypeAlias): E {
2017-11-02 20:39:34 +03:00
let schema = yield node.right;
if (typeof schema === 'string') {
schema = {type: schema};
}
2017-11-02 16:28:53 +03:00
schema.name = yield node.id;
2017-11-02 23:09:18 +03:00
schema.namespace = yield namespace();
2017-11-02 16:28:53 +03:00
2017-11-04 22:13:08 +03:00
yield define(schema);
return schema;
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* InterfaceDeclaration(node: t.InterfaceDeclaration): E {
2017-11-03 11:00:34 +03:00
let schema = yield node.body;
if (node.extends.length > 0) {
const schemas = [];
for (const extend of node.extends) {
const name = yield extend;
const schema = yield query(name);
schemas.push(schema);
}
schemas.push(schema);
schema = mergeSchemas(schemas);
}
2017-11-02 16:28:53 +03:00
schema.name = yield node.id;
2017-11-02 23:09:18 +03:00
schema.namespace = yield namespace();
2017-11-02 16:28:53 +03:00
2017-11-04 22:13:08 +03:00
yield define(schema);
return schema;
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* ClassDeclaration(node: t.ClassDeclaration): E {
2017-11-03 10:52:53 +03:00
let schema = yield node.body;
if (node.superClass) {
const name = yield node.superClass;
const superSchema = yield query(name);
schema = mergeSchemas([superSchema, schema]);
}
2017-11-02 16:28:53 +03:00
schema.name = yield node.id;
2017-11-02 23:09:18 +03:00
schema.namespace = yield namespace();
2017-11-02 16:28:53 +03:00
2017-11-04 22:13:08 +03:00
yield define(schema);
return schema;
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* ClassBody(node: t.ClassBody): E {
2017-10-29 01:55:39 +03:00
return {
2017-10-29 14:18:16 +03:00
type: 'record',
2017-11-02 16:28:53 +03:00
fields: (yield node.body).filter(Boolean),
2017-10-29 01:55:39 +03:00
};
},
2017-11-18 12:38:34 +03:00
* ClassProperty(node: t.ClassProperty): E {
2017-10-29 01:55:39 +03:00
if (node.static) {
return null;
}
2017-11-03 10:41:36 +03:00
return yield* extractProperty(node, node.typeAnnotation);
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* ClassMethod(node: t.ClassMethod): E {
2017-10-29 01:55:39 +03:00
return null;
},
2017-11-18 12:38:34 +03:00
* ObjectTypeAnnotation(node: t.ObjectTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
if (node.indexers.length > 0) {
// Allow functions, getters and setters.
2017-11-02 16:28:53 +03:00
const properties = (yield node.properties).filter(Boolean);
2017-10-29 01:55:39 +03:00
2017-11-18 12:38:34 +03:00
invariant(properties.length === 0);
invariant(node.indexers.length === 1);
2017-10-29 01:55:39 +03:00
return {
type: 'map',
2017-11-02 16:28:53 +03:00
values: yield node.indexers[0],
2017-10-29 01:55:39 +03:00
};
}
return {
type: 'record',
2017-11-02 16:28:53 +03:00
fields: (yield node.properties).filter(Boolean),
2017-10-29 01:55:39 +03:00
};
},
2017-11-18 12:38:34 +03:00
* ObjectTypeProperty(node: t.ObjectTypeProperty): E {
2017-11-03 10:41:36 +03:00
return yield* extractProperty(node, node.value);
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* ObjectTypeIndexer(node: t.ObjectTypeIndexer): E {
2017-11-02 16:28:53 +03:00
const key = yield node.key;
2017-10-29 01:55:39 +03:00
2017-11-18 12:38:34 +03:00
invariant(key === 'string');
2017-10-29 01:55:39 +03:00
2017-11-02 16:28:53 +03:00
return yield node.value;
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* TypeAnnotation(node: t.TypeAnnotation): E {
2017-11-02 16:28:53 +03:00
return yield node.typeAnnotation;
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* NumberTypeAnnotation(node: t.NumberTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
return 'double';
},
2017-11-18 12:38:34 +03:00
* StringTypeAnnotation(node: t.StringTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
return 'string';
},
2017-11-18 12:38:34 +03:00
* BooleanTypeAnnotation(node: t.BooleanTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
return 'boolean';
},
2017-11-18 12:38:34 +03:00
* ArrayTypeAnnotation(node: t.ArrayTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
return {
type: 'array',
2017-11-02 16:28:53 +03:00
items: yield node.elementType,
2017-10-29 01:55:39 +03:00
};
},
2017-11-18 12:38:34 +03:00
* UnionTypeAnnotation(node: t.UnionTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
// TODO: flatten variants.
let [symbols, variants] = partition(node.types, isEnumSymbol);
symbols = symbols.map(unwrapEnumSymbol);
2017-11-02 16:28:53 +03:00
variants = yield variants;
2017-10-29 01:55:39 +03:00
if (symbols.length > 0) {
const enumeration = {
type: 'enum',
symbols,
};
if (variants.length === 0) {
return enumeration;
}
variants.push(enumeration);
}
return variants;
},
2017-11-18 12:38:34 +03:00
* IntersectionTypeAnnotation(node: t.IntersectionTypeAnnotation): E {
2017-11-03 10:34:36 +03:00
const schemas = [];
for (const type of node.types) {
2017-11-18 12:38:34 +03:00
// TODO: support arbitrary types, not only references.
2017-11-03 10:34:36 +03:00
const name = yield type;
const schema = yield query(name);
schemas.push(schema);
}
return mergeSchemas(schemas);
},
2017-11-18 12:38:34 +03:00
* NullableTypeAnnotation(node: t.NullableTypeAnnotation): E {
2017-11-02 16:28:53 +03:00
return ['null', yield node.typeAnnotation];
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* NullLiteralTypeAnnotation(node: t.NullLiteralTypeAnnotation): E {
2017-10-29 14:34:19 +03:00
return 'null';
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* StringLiteralTypeAnnotation(node: t.StringLiteralTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
return {
type: 'enum',
symbols: [node.value],
};
},
2017-11-18 12:38:34 +03:00
* GenericTypeAnnotation(node: t.GenericTypeAnnotation): E {
2017-11-02 16:28:53 +03:00
const name = yield node.id;
2017-11-13 21:11:18 +03:00
const params = node.typeParameters && (yield node.typeParameters);
2017-11-13 21:11:18 +03:00
const schema = yield query(name, params);
if (typeof schema === 'string') {
return schema;
}
2017-11-06 02:13:41 +03:00
if (schema.$unwrap) {
return schema.type;
}
2017-11-02 23:09:18 +03:00
const enclosing = yield namespace();
2017-11-02 20:21:56 +03:00
2017-11-02 23:09:18 +03:00
if (schema.namespace === enclosing) {
return schema.name;
}
return makeFullname(schema);
2017-10-29 01:55:39 +03:00
},
2017-11-18 12:38:34 +03:00
* TypeParameterInstantiation(node: t.TypeParameterInstantiation): E {
2017-11-13 21:11:18 +03:00
return yield node.params;
},
2017-11-18 12:38:34 +03:00
* FunctionTypeAnnotation(node: t.FunctionTypeAnnotation): E {
2017-10-29 01:55:39 +03:00
return null;
},
2017-11-18 12:38:34 +03:00
* InterfaceExtends(node: t.InterfaceExtends): E {
2017-11-03 11:00:34 +03:00
return yield node.id;
},
2017-11-18 12:38:34 +03:00
* Identifier(node: t.Identifier): E {
2017-10-29 01:55:39 +03:00
return node.name;
},
2017-11-18 12:38:34 +03:00
* CommentLine(node: t.CommentLine): E {
return extractPragma(node.value);
},
2017-10-29 01:55:39 +03:00
2017-11-18 12:38:34 +03:00
* CommentBlock(node: t.CommentBlock): E {
return extractPragma(node.value);
2017-10-29 01:55:39 +03:00
},
2017-11-06 14:29:26 +03:00
};
2017-11-04 22:13:08 +03:00
2017-11-18 12:38:34 +03:00
function* extractLastPragma(comments: t.Comment[]): ?string {
2017-11-02 16:28:53 +03:00
const pragmas = (yield comments).filter(Boolean);
return pragmas.length > 0 ? pragmas[pragmas.length - 1] : null;
}
2017-11-03 10:41:36 +03:00
function* extractProperty(prop, value) {
let type = null;
if (prop.leadingComments) {
type = yield* extractLastPragma(prop.leadingComments);
}
if (!type) {
type = yield value;
}
if (!type) {
return null;
}
if (type.type === 'record') {
type.namespace = yield namespace();
2017-11-09 12:10:21 +03:00
yield define(type, false);
2017-11-03 10:41:36 +03:00
type = type.name;
}
return {
name: yield prop.key,
type,
};
}
2017-10-29 10:50:51 +03:00
function parsePragma(pragma) {
let [type, arg] = pragma.split(/\s+/);
2017-10-29 01:55:39 +03:00
2017-11-06 02:13:41 +03:00
if (isPrimitive(type)) {
if (arg != null) {
return null;
}
} else if (type === 'fixed') {
arg = Number(arg);
2017-10-29 10:50:51 +03:00
2017-11-06 02:13:41 +03:00
if (!Number.isInteger(arg)) {
2017-10-29 10:50:51 +03:00
return null;
2017-11-06 02:13:41 +03:00
}
} else {
return null;
2017-10-29 01:55:39 +03:00
}
2017-10-29 10:50:51 +03:00
return [type, arg];
2017-10-29 01:55:39 +03:00
}
function extractPragma(text) {
const marker = '$avro ';
const value = text.trimLeft();
2017-10-29 10:50:51 +03:00
if (!value.startsWith(marker)) {
return null;
}
const pragma = value.slice(marker.length).trim();
const pair = parsePragma(pragma);
2017-11-18 12:38:34 +03:00
invariant(pair);
2017-10-29 10:50:51 +03:00
const [type, arg] = pair;
if (type === 'fixed') {
return {
type: 'fixed',
size: arg,
};
}
2017-10-29 10:50:51 +03:00
return type;
}
2017-10-29 01:55:39 +03:00
function isEnumSymbol(node) {
return node.type === 'StringLiteralTypeAnnotation';
}
2017-11-06 02:13:41 +03:00
function isPrimitive(type) {
switch (type) {
case 'null':
case 'int':
case 'long':
case 'float':
case 'double':
case 'bytes':
case 'string':
case 'boolean':
return true;
default:
return false;
}
}
2017-10-29 01:55:39 +03:00
function unwrapEnumSymbol(node) {
return node.value;
}
2017-11-02 23:09:18 +03:00
function makeFullname(schema) {
2017-11-18 12:38:34 +03:00
invariant(schema.namespace);
2017-11-02 23:09:18 +03:00
return `${schema.namespace}.${schema.name}`;
}
function mergeSchemas(schemas: Schema[]): Schema {
2017-11-03 10:34:36 +03:00
const map = new Map;
// TODO: overriding?
2017-11-03 10:52:53 +03:00
// TODO: anonymous?
2017-11-03 10:34:36 +03:00
let name = '';
for (const schema of schemas) {
// TODO: enums?
2017-11-18 12:38:34 +03:00
invariant(schema.type === 'record');
2017-11-03 10:34:36 +03:00
for (const field of schema.fields) {
const stored = map.get(field.name);
if (stored) {
// TODO: what about enums?
// TODO: improve checking.
2017-11-18 12:38:34 +03:00
invariant(stored.type === field.type);
2017-11-03 10:34:36 +03:00
continue;
}
map.set(field.name, field);
}
name += '_' + schema.name;
}
return {
type: 'record',
name,
fields: Array.from(map.values()),
};
}