diff --git a/.flowconfig b/.flowconfig index 039d28f..eb9b40e 100644 --- a/.flowconfig +++ b/.flowconfig @@ -16,3 +16,7 @@ module.use_strict=true munge_underscores=true include_warnings=true unsafe.enable_getters_and_setters=true +suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe +suppress_comment= \\(.\\|\n\\)*\\$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FlowIssue diff --git a/README.md b/README.md index 9be2419..290ea00 100644 --- a/README.md +++ b/README.md @@ -5,43 +5,6 @@ [![Windows Build](https://ci.appveyor.com/api/projects/status/github/loyd/flow2schema?branch=master&svg=true)](https://ci.appveyor.com/project/loyd/flow2schema) [![Coverage Status](https://coveralls.io/repos/github/loyd/flow2schema/badge.svg?branch=master)](https://coveralls.io/r/loyd/flow2schema?branch=master) -Currently avro is the only supported target. - -## Example - -Input (`$ cat example.js`): -```javascript -export interface Foo { - foo: string, - // $avro long - bar: number, - opt: ?number, - baz: 'one' | 'two', - mix: 'one' | 'two' | number, -} -``` - -Output (`$ ./bin/flow2schema example.js`): -```json -[ - { - "type": "record", - "fields": [ - {"name": "foo", "type": "string"}, - {"name": "bar", "type": "double"}, - {"name": "opt", "type": ["null", "double"]}, - {"name": "baz", "type": {"type": "enum", "symbols": ["one", "two"]}}, - { - "name": "mix", - "type": ["double", {"type": "enum", "symbols": ["one", "two"]}] - } - ], - "name": "Foo", - "namespace": "example" - } -] -``` - ## TODO * Complete generics support. * Errors and warnings. diff --git a/declarations/wu.js b/declarations/wu.js new file mode 100644 index 0000000..d0054a6 --- /dev/null +++ b/declarations/wu.js @@ -0,0 +1,26 @@ +declare module 'wu' { + declare type DeepIterable = Iterable>; + declare type Flat = $Call<(Iterable | U) => U, T>; + declare type DeepFlat = $Call<(DeepIterable | U) => U, T>; + + declare export default class Wu { + static (Iterable): Wu; + + tap(T => mixed): Wu; + + map(T => U): Wu; + + filter(): Wu<$NonMaybeType>; + filter(T => boolean): Wu; + + flatten(): Wu>; + flatten(true): Wu>; + flatten(false): Wu>; + + find(T => boolean): T | void; + + pluck>(K): Wu<$ElementType>; + + toArray(): T[]; + } +} diff --git a/package.json b/package.json index d05d508..c5c7e2f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "babylon": "^7.0.0-beta.32", "json-stringify-pretty-compact": "^1.0.4", "optimist": "^0.6.1", - "resolve": "^1.5.0" + "resolve": "^1.5.0", + "wu": "^2.1.0" }, "devDependencies": { "@babel/cli": "^7.0.0-beta.32", diff --git a/src/cli.js b/src/cli.js index e9264e9..c271868 100644 --- a/src/cli.js +++ b/src/cli.js @@ -15,9 +15,9 @@ function run(path: string) { } try { - const {schemas} = collect(path); + const {types} = collect(path); - console.log(stringify(schemas, {maxLength: 100})); + console.log(stringify(types, {maxLength: 100})); } catch (ex) { console.error(ex.message); console.error(ex.stack); diff --git a/src/collector.js b/src/collector.js index 1a4d40c..fbc3613 100644 --- a/src/collector.js +++ b/src/collector.js @@ -10,28 +10,24 @@ import declarationGroup from './declarations'; import Module from './module'; import Scope from './scope'; import Context from './context'; -import {invariant, get, map} from './utils'; +import {invariant} from './utils'; import type Parser from './parser'; -import type {Schema, Type} from './schema'; - -type InstanceParam = { - name: string, - value: ?Type, -}; +import type {Type, TypeId} from './types'; +import type {TemplateParam} from './query'; const VISITOR = Object.assign({}, definitionGroup, declarationGroup); export default class Collector { +root: string; +parser: Parser; - +schemas: Schema[]; + +types: Type[]; _modules: Map; _global: Scope; constructor(parser: Parser, root: string = '.') { this.root = root; this.parser = parser; - this.schemas = []; + this.types = []; this._modules = new Map; this._global = Scope.global(globals); } @@ -51,9 +47,9 @@ export default class Collector { const ast = this.parser.parse(code); // TODO: customize it. - const namespace = pathToNamespace(pathlib.relative('.', path)); + const id = pathToId(path); - module = new Module(path, namespace); + module = new Module(id, path); const scope = this._global.extend(module); @@ -66,7 +62,7 @@ export default class Collector { } } - _freestyle(root: Node, scope: Scope, params: InstanceParam[]) { + _freestyle(root: Node, scope: Scope, params: TemplateParam[]) { const ctx = new Context(this, scope, params); const iter = traverse(root); @@ -88,7 +84,7 @@ export default class Collector { let result = scope.query(name, params); // TODO: warning. - invariant(result.type !== 'unknown'); + invariant(result.kind !== 'unknown'); // Resulting scope is always the best choice for waiting. scope = result.scope; @@ -96,7 +92,7 @@ export default class Collector { // It's only valid the sequence: E*[CT]?F, // where E - external, C - declaration, T - template, F - definition. - switch (result.type) { + switch (result.kind) { case 'external': const modulePath = scope.resolve(result.info.path); @@ -110,12 +106,12 @@ export default class Collector { result = module.query(imported, params); - if (result.type === 'definition') { - return result.schema; + if (result.kind === 'definition') { + return result.type; } // TODO: reexports. - invariant(result.type === 'declaration' || result.type === 'template'); + invariant(result.kind === 'declaration' || result.kind === 'template'); scope = result.scope; name = result.name; @@ -125,26 +121,26 @@ export default class Collector { case 'template': const tmplParams = []; - if (result.type === 'template') { + if (result.kind === 'template') { for (const [i, p] of result.params.entries()) { tmplParams.push({ name: p.name, - value: params[i] === undefined ? p.default : params[i], + value: params[i] === undefined ? p.value : params[i], }); } } - invariant(result.type === 'declaration' || result.type === 'template'); + invariant(result.kind === 'declaration' || result.kind === 'template'); this._freestyle(result.node, scope, tmplParams); result = scope.query(name, params); - invariant(result.type === 'definition'); + invariant(result.kind === 'definition'); // Fallthrough. case 'definition': - return result.schema; + return result.type; } invariant(false); @@ -157,14 +153,14 @@ export default class Collector { } } -function pathToNamespace(path: string): string { - const pathObj = pathlib.parse(path); +function pathToId(path: string): TypeId { + const relPath = pathlib.relative('.', path); + const pathObj = pathlib.parse(relPath); return pathlib.format({ dir: pathObj.dir, name: pathObj.name, }) // TODO: replace invalid chars. - .split(pathlib.sep) - .join('.'); + .split(pathlib.sep); } diff --git a/src/context.js b/src/context.js index cd84d82..dd74414 100644 --- a/src/context.js +++ b/src/context.js @@ -1,31 +1,23 @@ +import wu from 'wu'; import type {Node} from '@babel/types'; import Scope from './scope'; import Collector from './collector'; -import {invariant, get, map} from './utils'; -import type {Schema, Type, ComplexType} from './schema'; +import {invariant} from './utils'; +import type {Type} from './types'; import type {TemplateParam, ExternalInfo} from './query'; -type InstanceParam = { - name: string, - value: ?Type, -}; - export default class Context { _collector: Collector; _scope: Scope; - _params: InstanceParam[]; + _params: TemplateParam[]; - constructor(collector: Collector, scope: Scope, params: InstanceParam[]) { + constructor(collector: Collector, scope: Scope, params: TemplateParam[]) { this._collector = collector; this._scope = scope; this._params = params; } - get namespace(): string { - return this._scope.namespace; - } - freestyle(node: Node) { this._collector._freestyle(node, this._scope, this._params); } @@ -34,26 +26,18 @@ export default class Context { this._scope.addDeclaration(name, node, params || []); } - define(name: string, type: ComplexType, declared: boolean = true): Schema { - // TODO: dirty support for intersections. - const _name = type.name != null ? type.name : name; - - const schema: $FlowFixMe = Object.assign({}, type, { - name, - namespace: this._scope.namespace, - }); - + define(name: string, type: Type, declared: boolean = true): Type { if (declared && this._params.length > 0) { - schema.name = generateGenericName(name, this._params); + const params = wu(this._params).pluck('value').toArray(); - this._scope.addInstance(name, schema, map(this._params, get('value'))); + this._scope.addInstance(name, type, params); } else { - this._scope.addDefinition(schema, declared); + this._scope.addDefinition(name, type, declared); } - this._collector.schemas.push(schema); + this._collector.types.push(type); - return schema; + return type; } external(external: ExternalInfo) { @@ -65,7 +49,7 @@ export default class Context { } query(name: string, params: ?(?Type)[]): Type { - const param = this._params.find(p => p.name === name); + const param = wu(this._params).find(p => p.name === name); if (param) { // TODO: warning about missing param. @@ -86,14 +70,3 @@ export default class Context { this._scope = this._scope.parent; } } - -function generateGenericName(base: string, params: InstanceParam[]): string { - let name = base + '_'; - - for (const {value} of params) { - invariant(typeof value === 'string'); - name += '_' + value; - } - - return name; -} diff --git a/src/declarations.js b/src/declarations.js index bbc7a05..4ef5469 100644 --- a/src/declarations.js +++ b/src/declarations.js @@ -1,4 +1,6 @@ -// flow#5376. +import wu from 'wu'; + +// @see flow#5376. import type { Block, ClassDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, Identifier, ImportDeclaration, ImportDefaultSpecifier, ImportSpecifier, InterfaceDeclaration, @@ -12,7 +14,7 @@ import { isStringLiteral, isTypeAlias, isVariableDeclaration, } from '@babel/types'; -import {invariant, map, filter} from './utils'; +import {invariant} from './utils'; import Context from './context'; import type {ExternalInfo, TemplateParam} from './query'; @@ -146,7 +148,10 @@ function extractCommonjsNamedExternals<+T: Node>(nodes: T[], path: string): Exte path, }); - return map(filter(nodes, pred), make); + return wu(nodes) + .filter(pred) + .map(make) + .toArray(); } /* @@ -201,7 +206,7 @@ function processDeclaration(ctx: Context, node: Declaration) { // TODO: do it only for "all"-mode. if (isClassDeclaration(node)) { - const methods = filter(node.body.body, isClassMethod); + const methods = wu(node.body.body).filter(isClassMethod).toArray(); for (const method of methods) { ctx.freestyle(method); @@ -214,10 +219,13 @@ function processDeclaration(ctx: Context, node: Declaration) { } function extractTemplateParams(node: TypeParameterDeclaration): TemplateParam[] { - return map(node.params, param => ({ - name: param.name, - default: null, - })); + return wu(node.params) + .map(param => ({ + name: param.name, + // TODO: default params. + value: null, + })) + .toArray(); } export default { diff --git a/src/definitions.js b/src/definitions.js index b8faedb..b176015 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -1,9 +1,11 @@ -// flow#5376. +import wu from 'wu'; + +// @see flow#5376. import type { ArrayTypeAnnotation, ClassDeclaration, ClassProperty, Comment, FlowTypeAnnotation, GenericTypeAnnotation, InterfaceDeclaration, IntersectionTypeAnnotation, TypeAlias, UnionTypeAnnotation, NullableTypeAnnotation, ObjectTypeIndexer, ObjectTypeProperty, - StringLiteralTypeAnnotation, + StringLiteralTypeAnnotation, ObjectTypeAnnotation, } from '@babel/types'; import { @@ -13,98 +15,104 @@ import { import Context from './context'; import type { - Schema, Type, ReferenceType, ComplexType, RecordType, FixedType, - FieldType, ArrayType, MapType, UnionType, EnumType, -} from './schema'; + Type, RecordType, Field, ArrayType, TupleType, MapType, UnionType, IntersectionType, + MaybeType, NumberType, StringType, BooleanType, LiteralType, ReferenceType, +} from './types'; -import {isPrimitiveType, isComplexType, makeFullname, mergeTypes} from './schema'; -import {invariant, compose, last, get, partition, map, filter, filterMap, compact} from './utils'; +import {extractPragmas} from './pragmas'; + +import {invariant} from './utils'; function processTypeAlias(ctx: Context, node: TypeAlias) { - let type = makeType(ctx, node.right); + const {name} = node.id; + const type = makeType(ctx, node.right); // TODO: support function aliases. - invariant(type != null); - // TODO: support top-level unions. - invariant(!(type instanceof Array)); + invariant(type); - if (typeof type === 'string') { - type = {type: type}; - } - - ctx.define(node.id.name, type); + ctx.define(name, type); } // TODO: type params. function processInterfaceDeclaration(ctx: Context, node: InterfaceDeclaration) { - let type = makeType(ctx, node.body); + const {name} = node.id; + const type = makeType(ctx, node.body); - invariant(type != null) - invariant(isComplexType(type)); + invariant(type); - if (node.extends.length > 0) { - const types = []; - - for (const extend of node.extends) { - const name = extend.id.name; - const type = ctx.query(name); - - invariant(isComplexType(type)); - - types.push((type: $FlowFixMe)); - } - - types.push((type: $FlowFixMe)); - - [, type] = mergeTypes(types); + if (node.extends.length === 0) { + ctx.define(name, type); + return; } - ctx.define(node.id.name, type); + const parts = []; + + for (const extend of node.extends) { + const {name} = extend.id; + const type = ctx.query(name); + + invariant(type.id); + + parts.push({ + kind: 'reference', + to: type.id, + }); + } + + parts.push(type); + + ctx.define(name, { + kind: 'intersection', + parts, + }); } // TODO: type params. function processClassDeclaration(ctx: Context, node: ClassDeclaration) { - const props: $FlowFixMe = filter(node.body.body, isClassProperty); + const props: $FlowFixMe = wu(node.body.body).filter(isClassProperty).toArray(); - let type = makeRecord(ctx, props); + const {name} = node.id; + const type = makeRecord(ctx, props); - if (node.superClass) { - // TODO: warning about expressions here. - invariant(isIdentifier(node.superClass)); - - const {name} = node.superClass; - const superSchema = ctx.query(name); - - invariant(isComplexType(superSchema)); - - [, type] = mergeTypes([(superSchema: $FlowFixMe), (type: $FlowFixMe)]); + if (!node.superClass) { + ctx.define(name, type); + return; } - ctx.define(node.id.name, type); + // TODO: warning about expressions here. + invariant(isIdentifier(node.superClass)); + + const base = ctx.query(node.superClass.name); + + invariant(base.id); + + const baseRef = { + kind: 'reference', + to: base.id, + }; + + ctx.define(name, { + kind: 'intersection', + parts: [baseRef, type], + }); } function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { switch (node.type) { case 'NullLiteralTypeAnnotation': - return 'null'; + return {kind: 'literal', value: null}; case 'BooleanTypeAnnotation': - return 'boolean'; + return {kind: 'boolean'}; case 'NumberTypeAnnotation': - return 'double'; + return {kind: 'number', repr: 'f64'}; case 'StringTypeAnnotation': - return 'string'; + return {kind: 'string'}; case 'TypeAnnotation': return makeType(ctx, node.typeAnnotation); case 'NullableTypeAnnotation': - return makeNullable(ctx, node); + return makeMaybe(ctx, node); case 'ObjectTypeAnnotation': - const map = makeMap(ctx, node.indexers); - const record = makeRecord(ctx, node.properties); - - // TODO: warning about this. - invariant(!map || record.fields.length === 0); - - return map || record; + return makeComplexType(ctx, node); case 'ArrayTypeAnnotation': return makeArrayType(ctx, node); case 'UnionTypeAnnotation': @@ -112,50 +120,85 @@ function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { case 'IntersectionTypeAnnotation': return makeIntersection(ctx, node); case 'StringLiteralTypeAnnotation': - return makeEnum(node); + return {kind: 'literal', value: node.value}; case 'GenericTypeAnnotation': return makeReference(ctx, node); case 'FunctionTypeAnnotation': return null; default: - invariant(false, `Unknown type: ${node.type}`); + invariant(false, `Unknown node: ${node.type}`); } } -function makeNullable(ctx: Context, node: NullableTypeAnnotation): ?UnionType { +function makeMaybe(ctx: Context, node: NullableTypeAnnotation): ?MaybeType { const type = makeType(ctx, node.typeAnnotation); - if (type == null) { + if (!type) { return null; } - return ['null', type]; + 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, + }; } function makeRecord(ctx: Context, nodes: T[]): RecordType { - const fields = compact(map(nodes, node => makeField(ctx, node))); + const fields = wu(nodes) + .map(node => makeField(ctx, node)) + .filter() + .toArray(); return { - type: 'record', + kind: 'record', fields, }; } -function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?FieldType { - // $FlowFixMe - if (node.static) { +function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Field { + if ((node: $FlowIssue<3129>).static) { return null; } let type = null; if (node.leadingComments) { - const pragmas = extractPragmas(node.leadingComments); + const pragma = (wu: $FlowIssue<4431>)(node.leadingComments) + .pluck('value') + .map(extractPragmas) + .flatten() + .find(pragma => pragma.kind === 'type'); - type = last(pragmas); + if (pragma) { + type = pragma.value; + } } - if (type == null) { + if (!type) { const value = isObjectTypeProperty(node) ? node.value : node.typeAnnotation; // TODO: no type annotation for the class property. @@ -164,17 +207,10 @@ function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Fie type = makeType(ctx, value); } - if (type == null) { + if (!type) { return null; } - if (isComplexType(type) && type.type === 'record') { - const name = (type: $FlowFixMe).name; - ctx.define(name, type, false); - type = name; - } - - // TODO: support optional fields. // TODO: warning about computed properties. invariant(isObjectTypeProperty(node) || !node.computed); @@ -182,30 +218,22 @@ function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Fie return { name: node.key.name, - type, + value: type, + required: node.optional == null || !node.optional, }; } -function makeMap(ctx: Context, nodes: ObjectTypeIndexer[]): ?MapType { - if (nodes.length === 0) { - return null; - } - - // TODO: what to do in this case? - invariant(nodes.length === 1); - - const node = nodes[0]; - - invariant(makeType(ctx, node.key) === 'string'); - +function makeMap(ctx: Context, node: ObjectTypeIndexer): ?MapType { + const keys = makeType(ctx, node.key); const values = makeType(ctx, node.value); - if (values == null) { + if (!(keys && values)) { return null; } return { - type: 'map', + kind: 'map', + keys, values, }; } @@ -213,151 +241,68 @@ function makeMap(ctx: Context, nodes: ObjectTypeIndexer[]): ?MapType { function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType { const items = makeType(ctx, node.elementType); - if (items == null) { + if (!items) { return null; } return { - type: 'array', + kind: 'array', items, }; } -function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?(UnionType | EnumType) { - // TODO: flatten variants. - // TODO: refactor it. - - let [symbols, variants] = partition(node.types, isStringLiteralTypeAnnotation); - - // $FlowFixMe - symbols = map(symbols, get('value')); - variants = compact(map(variants, node => makeType(ctx, node))); - - if (symbols.length > 0) { - const enumeration: EnumType = { - type: 'enum', - symbols, - }; - - if (variants.length === 0) { - return enumeration; - } - - variants.push(enumeration); - } +function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?UnionType { + const variants = wu(node.types) + .map(node => makeType(ctx, node)) + .filter() + .toArray(); if (variants.length === 0) { return null; } - return variants; -} - -function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type { - const types = []; - - // TODO: refactor it. - for (const typeNode of node.types) { - const type = makeType(ctx, typeNode); - - if (type == null) { - continue; - } - - // TODO: support arbitrary types, not only references. - invariant(typeof type === 'string'); - - const queried = ctx.query(type); - - invariant(isComplexType(queried)); - - types.push((queried: $FlowFixMe)); - } - - if (types.length === 0) { - return null; - } - - const [name, intersection] = mergeTypes(types); - - // TODO: dirty support for intersections. - (intersection: $FlowFixMe).name = name; - - return intersection; -} - -function makeEnum(node: StringLiteralTypeAnnotation): EnumType { return { - type: 'enum', - symbols: [node.value], + kind: 'union', + variants, }; } -function makeReference(ctx: Context, node: GenericTypeAnnotation): ReferenceType { - const {name} = node.id; - const params = node.typeParameters && map(node.typeParameters.params, n => makeType(ctx, n)); +function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type { + // TODO: warning about nulls. + const parts = wu(node.types) + .map(node => makeType(ctx, node)) + .filter() + .toArray(); - const type = ctx.query(name, params); - - if (typeof type === 'string') { - return type; - } - - // TODO: generalized it. - if ((type: $FlowFixMe).$unwrap) { - invariant(typeof type.type === 'string'); - - return type.type; - } - - invariant(isComplexType(type)); - - if (type.namespace === ctx.namespace) { - return (type: $FlowFixMe).name; - } - - return makeFullname((type: $FlowFixMe)); -} - -function extractPragmas(comments: Comment[]): Type[] { - return filterMap(comments, compose(get('value'), extractPragma)); -} - -function extractPragma(text: string): ?Type { - const marker = '$avro '; - - const value = text.trimLeft(); - - if (!value.startsWith(marker)) { + if (parts.length === 0) { return null; } - const pragma = value.slice(marker.length).trim(); + if (parts.length === 1) { + return parts[0]; + } - return parsePragma(pragma); + return { + kind: 'intersection', + parts, + }; } -function parsePragma(pragma: string): Type { - let [type, arg] = pragma.split(/\s+/); +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(); - if (isPrimitiveType(type)) { - invariant(arg == null); + const type = ctx.query(name, params); + if (!type.id) { return type; } - if (type === 'fixed') { - arg = Number(arg); - - invariant(Number.isInteger(arg)); - - return ({ - type: 'fixed', - size: arg, - }: FixedType); - } - - invariant(false); + return { + kind: 'reference', + to: type.id, + }; } export default { diff --git a/src/globals.js b/src/globals.js index 857924f..98ac070 100644 --- a/src/globals.js +++ b/src/globals.js @@ -1,7 +1,7 @@ export default [ { - name: 'Buffer', - type: 'bytes', - $unwrap: true, + id: ['Buffer'], + kind: 'record', + fields: [], }, ]; diff --git a/src/module.js b/src/module.js index c9b5068..77c09d6 100644 --- a/src/module.js +++ b/src/module.js @@ -2,24 +2,29 @@ import * as pathlib from 'path'; import * as resolve from 'resolve'; import type Scope from './scope'; -import type {Schema, Type} from './schema'; +import type {Type, TypeId} from './types'; import type {Query} from './query'; export default class Module { + +id: TypeId; +path: string; - +namespace: string; _scopeCount: number; _exports: Map; - constructor(path: string, namespace: string) { + constructor(id: TypeId, path: string) { + this.id = id; this.path = path; - this.namespace = namespace; this._scopeCount = 0; this._exports = new Map; } - generateScopeId(): number { - return this._scopeCount++; + generateScopeId(): TypeId { + if (this._scopeCount === 0) { + ++this._scopeCount; + return this.id; + } + + return this.id.concat(String(this._scopeCount++)); } addExport(name: ?string, scope: Scope, reference: string) { @@ -31,7 +36,7 @@ export default class Module { if (!result) { return { - type: 'unknown', + kind: 'unknown', }; } diff --git a/src/pragmas.js b/src/pragmas.js new file mode 100644 index 0000000..58bdcf8 --- /dev/null +++ b/src/pragmas.js @@ -0,0 +1,38 @@ +import {invariant} from './utils'; + +import type {Type} from './types'; + +export type Pragma = + | TypePragma; + +export type TypePragma = { + kind: 'type', + value: Type, +}; + +const PRAGMA_RE = /^\s*@repr\s+\{\s*(.+?)\s*\}\s*$/gm; + +export function extractPragmas(text: string): Pragma[] { + const pragmas = []; + let match; + + while ((match = PRAGMA_RE.exec(text))) { + const repr = match[1]; + + invariant(['i32', 'i64', 'u32', 'u64', 'f32', 'f64'].includes(repr)); + + const pragma = { + kind: 'type', + value: { + kind: 'number', + repr, + }, + }; + + if (pragma) { + pragmas.push(pragma); + } + } + + return pragmas; +} diff --git a/src/query.js b/src/query.js index adecbaf..aff56ae 100644 --- a/src/query.js +++ b/src/query.js @@ -1,7 +1,7 @@ import type {Node} from '@babel/types'; import type Scope from './scope'; -import type {Schema, Type} from './schema'; +import type {Type} from './types'; export type Query = | Unknown @@ -11,18 +11,18 @@ export type Query = | External; export type Unknown = { - type: 'unknown', + kind: 'unknown', }; export type Declaration = { - type: 'declaration', + kind: 'declaration', name: string, node: Node, scope: Scope, }; export type Template = { - type: 'template', + kind: 'template', name: string, params: TemplateParam[], instances: Instance[], @@ -31,25 +31,25 @@ export type Template = { }; export type Definition = { - type: 'definition', - schema: Schema, + kind: 'definition', + type: Type, scope: Scope, }; export type External = { - type: 'external', + kind: 'external', info: ExternalInfo, scope: Scope, }; export type TemplateParam = { name: string, - default: ?Type, + value: ?Type, }; export type Instance = { params: (?Type)[], - schema: Schema, + type: Type, }; export type ExternalInfo = { diff --git a/src/schema.js b/src/schema.js deleted file mode 100644 index 7b4be85..0000000 --- a/src/schema.js +++ /dev/null @@ -1,147 +0,0 @@ -import {invariant} from './utils'; - -// @see flow#3912. -export type Schema = - | RecordType & Top - | EnumType & Top - | ArrayType & Top - | MapType & Top - // TODO: support top-level unions. - //| UnionType & Top - | FixedType & Top - | WrappedType & Top; - -export type Top = { - name: string, - namespace?: string, - $unwrap?: boolean, -}; - -export type Type = - | ComplexType - | UnionType - | PrimitiveType - | ReferenceType; - -export type WrappedType = {type: Type}; - -export type ComplexType = - | RecordType - | EnumType - | ArrayType - | MapType - // TODO: unions should be complex types. - //| UnionType & Top - | FixedType - | WrappedType; - -export type PrimitiveType = - | 'null' - | 'boolean' - | 'int' - | 'long' - | 'float' - | 'double' - | 'bytes' - | 'string'; - -export type ReferenceType = string; - -export type RecordType = { - type: 'record', - fields: FieldType[], -}; - -export type FieldType = { - name: string, - type: Type, -}; - -export type EnumType = { - type: 'enum', - symbols: string[], -}; - -export type ArrayType = { - type: 'array', - items: Type, -}; - -export type MapType = { - type: 'map', - values: Type, -}; - -export type UnionType = Type[]; - -export type FixedType = { - type: 'fixed', - size: number, -}; - -export function isPrimitiveType(type: Type): boolean %checks { - // Switch operator is not allowed in %checks functions. - return type === 'null' - || type === 'int' - || type === 'long' - || type === 'float' - || type === 'double' - || type === 'bytes' - || type === 'string' - || type === 'boolean'; -} - -export function isComplexType(type: Type): boolean %checks { - return typeof type !== 'string' && !(type instanceof Array); -} - -export function makeFullname(schema: Top): string { - invariant(schema.namespace != null); - - return `${schema.namespace}.${schema.name}`; -} - -export function mergeTypes<+T: ComplexType & {+name?: string}>(types: T[]): [string, ComplexType] { - invariant(types.length > 1); - - if (types.length === 1) { - const type = types[0]; - // TODO: anonymous? - invariant(type.name != null); - - return [type.name, (type: $FlowFixMe)]; - } - - const map = new Map; - - // TODO: overriding? - let name = ''; - - for (const type of types) { - // TODO: enums? - invariant(type.type === 'record'); - - for (const field of (type: $FlowFixMe).fields) { - const stored = map.get(field.name); - - if (stored) { - // TODO: what about enums? - // TODO: improve checking. - invariant(stored.type === field.type); - continue; - } - - map.set(field.name, field); - } - - // TODO: anonymous? - name += '_' + (type.name != null ? type.name : 'unnamed'); - } - - const type = { - type: 'record', - fields: Array.from(map.values()), - }; - - return [name, (type: $FlowFixMe)]; -} diff --git a/src/scope.js b/src/scope.js index 0e7e86d..c889593 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,46 +1,39 @@ +import wu from 'wu'; import type {Node} from '@babel/types'; -import {invariant} from './utils'; +import {invariant, last} from './utils'; import type Module from './module'; -import type {Schema, Type} from './schema'; +import type {Type, TypeId} from './types'; import type {Query, Template, TemplateParam, ExternalInfo} from './query'; export default class Scope { + +id: TypeId; +parent: ?Scope; +module: ?Module; - +scopeId: ?number; _entries: Map; - static global(schemas: Schema[]) { + static global(types: Type[]) { const global = new Scope(null, null); - for (const schema of schemas) { - global.addDefinition(schema, false); + for (const type of types) { + invariant(type.id); + + const name = last(type.id); + invariant(name != null); + + global.addDefinition(name, type, false); } return global; } constructor(parent: ?Scope, module: ?Module) { + this.id = module ? module.generateScopeId() : []; this.parent = parent; this.module = module; - this.scopeId = module && module.generateScopeId(); this._entries = new Map; } - get namespace(): string { - invariant(this.module); - - let namespace = this.module.namespace; - - // Nested scopes. - if (this.scopeId != null && this.scopeId > 0) { - namespace += '._' + this.scopeId; - } - - return namespace; - } - extend(module: ?Module = null): Scope { return new Scope(this, module || this.module); } @@ -49,14 +42,14 @@ export default class Scope { invariant(!this._entries.has(name)); const entry = params.length > 0 ? { - type: 'template', + kind: 'template', name, params, instances: [], node, scope: this, } : { - type: 'declaration', + kind: 'declaration', name, node, scope: this, @@ -65,28 +58,34 @@ export default class Scope { this._entries.set(name, entry); } - addInstance(name: string, schema: Schema, params: (?Type)[]) { + addInstance(name: string, type: Type, params: (?Type)[]) { const template = this._entries.get(name); invariant(template); - invariant(template.type === 'template'); + invariant(template.kind === 'template'); - template.instances.push({params, schema}); + const iname = generateGenericName(params); + + type.id = this.id.concat(name, iname); + + template.instances.push({params, type}); } - addDefinition(schema: Schema, declared: boolean) { - const decl = this._entries.get(schema.name); + addDefinition(name: string, type: Type, declared: boolean) { + const decl = this._entries.get(name); if (declared) { invariant(decl); - invariant(decl.type === 'declaration'); + invariant(decl.kind === 'declaration'); } else { invariant(!decl); } - this._entries.set(schema.name, { - type: 'definition', - schema, + type.id = this.id.concat(name); + + this._entries.set(name, { + kind: 'definition', + type, scope: this, }); } @@ -95,7 +94,7 @@ export default class Scope { invariant(!this._entries.has(info.local)); this._entries.set(info.local, { - type: 'external', + kind: 'external', info, scope: this, }); @@ -116,14 +115,14 @@ export default class Scope { query(name: string, params: (?Type)[]): Query { const entry = this._entries.get(name); - if (entry && entry.type === 'template') { - const augmented = entry.params.map((p, i) => params[i] === undefined ? p.default : params[i]); - const schema = findInstance(entry, augmented); + if (entry && entry.kind === 'template') { + const augmented = entry.params.map((p, i) => params[i] === undefined ? p.value : params[i]); + const type = findInstance(entry, augmented); - if (schema) { + if (type) { return { - type: 'definition', - schema, + kind: 'definition', + type, scope: entry.scope, }; } @@ -138,20 +137,46 @@ export default class Scope { } return { - type: 'unknown', + kind: 'unknown', }; } } -function findInstance(template: Template, queried: (?Type)[]): ?Schema { - for (const {schema, params} of template.instances) { +function findInstance(template: Template, queried: (?Type)[]): ?Type { + for (const {type, params} of template.instances) { // TODO: compare complex structures. const same = params.every((p, i) => p === queried[i]); if (same) { - return schema; + return type; } } return null; } + +function generateGenericName(params: (?Type)[]): TypeId { + return wu(params) + .map(type => { + invariant(type); + return getTypeName(type); + }) + .toArray(); +} + +function getTypeName(type: Type): string { + switch (type.kind) { + case 'reference': + const name = last(type.to); + invariant(name != null); + + return name; + case 'number': + return type.repr; + case 'string': + case 'boolean': + return type.kind; + default: + invariant(false); + } +} diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..2a77ee5 --- /dev/null +++ b/src/types.js @@ -0,0 +1,84 @@ +export type Type = + | RecordType + | ArrayType + | TupleType + | MapType + | UnionType + | IntersectionType + | MaybeType + | NumberType + | StringType + | BooleanType + | LiteralType + | ReferenceType; + +export type TypeId = string[]; + +export type BaseType = { + id?: TypeId, +}; + +export type RecordType = BaseType & { + kind: 'record', + fields: Field[], +}; + +export type Field = { + name: string, + value: Type, + required: boolean, +}; + +export type ArrayType = BaseType & { + kind: 'array', + items: Type, +}; + +export type TupleType = BaseType & { + kind: 'tuple', + items: Type[], +}; + +export type MapType = BaseType & { + kind: 'map', + keys: Type, + values: Type, +}; + +export type UnionType = BaseType & { + kind: 'union', + variants: Type[], +}; + +export type IntersectionType = BaseType & { + kind: 'intersection', + parts: Type[], +} + +export type MaybeType = BaseType & { + kind: 'maybe', + value: Type, +}; + +export type NumberType = BaseType & { + kind: 'number', + repr: 'i32' | 'i64' | 'u32' | 'u64' | 'f32' | 'f64', +}; + +export type StringType = BaseType & { + kind: 'string', +}; + +export type BooleanType = BaseType & { + kind: 'boolean', +}; + +export type LiteralType = BaseType & { + kind: 'literal', + value: string | number | boolean | null | void, +}; + +export type ReferenceType = BaseType & { + kind: 'reference', + to: TypeId, +}; diff --git a/src/utils.js b/src/utils.js index a587963..59ded68 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,67 +4,6 @@ import * as assert from 'assert'; // @see flow#112. export const invariant = assert.ok; -export function partition(iter: Iterable, predicate: T => boolean): [T[], T[]] { - const left = []; - const right = []; - - for (const item of iter) { - (predicate(item) ? left : right).push(item); - } - - return [left, right]; -} - -export function map(iter: Iterable, mapper: T => R): R[] { - const result = []; - - for (const item of iter) { - result.push(mapper(item)); - } - - return result; -} - -export function filter(iter: Iterable, predicate: T => boolean): T[] { - const result = []; - - for (const item of iter) { - if (predicate(item)) { - result.push(item); - } - } - - return result; -} - -export function filterMap(iter: Iterable, mapper: T => ?R): R[] { - const result = []; - - for (const item of iter) { - const it = mapper(item); - - if (it != null) { - result.push(it); - } - } - - return result; -} - -export function compact(iter: Iterable): T[] { - // $FlowFixMe - return filter(iter, Boolean); -} - -// $FlowFixMe -export function get>(key: K): T => $ElementType { - return obj => obj[key]; -} - -export function compose(a: X => Y, b: Y => Z): X => Z { - return x => b(a(x)); -} - export function last(list: T[]): T | void { return list.length > 0 ? list[list.length - 1] : undefined; } diff --git a/tests/run.js b/tests/run.js index 76a7677..b0b50cf 100644 --- a/tests/run.js +++ b/tests/run.js @@ -13,8 +13,8 @@ function run(title) { expected = JSON.parse(fs.readFileSync(title + '.json', 'utf8')); }); - it('should provide expected schemas', () => { - assert.deepEqual(actual.schemas, expected.schemas); + it('should provide expected types', () => { + assert.deepEqual(actual.types, expected.types); }); } diff --git a/tests/samples/arrays.js b/tests/samples/arrays.js index c505a7e..8be88ab 100644 --- a/tests/samples/arrays.js +++ b/tests/samples/arrays.js @@ -1,13 +1,13 @@ type Type = { - a: number[], + a: string[], }; interface Interface { - a: number[]; + a: string[]; } class Class { - a: number[]; + a: string[]; } export {Type, Interface, Class}; diff --git a/tests/samples/arrays.json b/tests/samples/arrays.json index 20d4092..0bc5219 100644 --- a/tests/samples/arrays.json +++ b/tests/samples/arrays.json @@ -1,30 +1,30 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "arrays", + "id": ["arrays", "Type"], + "kind": "record", "fields": [{ "name": "a", - "type": {"type": "array", "items": "double"} + "value": {"kind": "array", "items": {"kind": "string"}}, + "required": true }] }, { - "type": "record", - "name": "Interface", - "namespace": "arrays", + "id": ["arrays", "Interface"], + "kind": "record", "fields": [{ "name": "a", - "type": {"type": "array", "items": "double"} + "value": {"kind": "array", "items": {"kind": "string"}}, + "required": true }] }, { - "type": "record", - "name": "Class", - "namespace": "arrays", + "id": ["arrays", "Class"], + "kind": "record", "fields": [{ "name": "a", - "type": {"type": "array", "items": "double"} + "value": {"kind": "array", "items": {"kind": "string"}}, + "required": true }] } ] diff --git a/tests/samples/disorder.json b/tests/samples/disorder.json index 6542130..47437ab 100644 --- a/tests/samples/disorder.json +++ b/tests/samples/disorder.json @@ -1,21 +1,22 @@ { - "schemas": [ + "types": [ { - "type": "string", - "name": "Z", - "namespace": "disorder" + "id": ["disorder", "Z"], + "kind": "string" }, { - "type": "record", - "name": "Y", - "namespace": "disorder", - "fields": [{"name": "z", "type": "Z"}] + "id": ["disorder", "Y"], + "kind": "record", + "fields": [ + {"name": "z", "value": {"kind": "reference", "to": ["disorder", "Z"]}, "required": true} + ] }, { - "type": "record", - "name": "X", - "namespace": "disorder", - "fields": [{"name": "y", "type": "Y"}] + "id": ["disorder", "X"], + "kind": "record", + "fields": [ + {"name": "y", "value": {"kind": "reference", "to": ["disorder", "Y"]}, "required": true} + ] } ] } diff --git a/tests/samples/empty.json b/tests/samples/empty.json index 26692e6..e0d4d26 100644 --- a/tests/samples/empty.json +++ b/tests/samples/empty.json @@ -1,3 +1,3 @@ { - "schemas": [] + "types": [] } diff --git a/tests/samples/enums.json b/tests/samples/enums.json index fdf5a0f..21efc9c 100644 --- a/tests/samples/enums.json +++ b/tests/samples/enums.json @@ -1,30 +1,48 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "enums", + "id": ["enums", "Type"], + "kind": "record", "fields": [{ "name": "a", - "type": {"type": "enum", "symbols": ["one", "two"]} + "value": { + "kind": "union", + "variants": [ + {"kind": "literal", "value": "one"}, + {"kind": "literal", "value": "two"} + ] + }, + "required": true }] }, { - "type": "record", - "name": "Interface", - "namespace": "enums", + "id": ["enums", "Interface"], + "kind": "record", "fields": [{ "name": "a", - "type": {"type": "enum", "symbols": ["one", "two"]} + "value": { + "kind": "union", + "variants": [ + {"kind": "literal", "value": "one"}, + {"kind": "literal", "value": "two"} + ] + }, + "required": true }] }, { - "type": "record", - "name": "Class", - "namespace": "enums", + "id": ["enums", "Class"], + "kind": "record", "fields": [{ "name": "a", - "type": {"type": "enum", "symbols": ["one", "two"]} + "value": { + "kind": "union", + "variants": [ + {"kind": "literal", "value": "one"}, + {"kind": "literal", "value": "two"} + ] + }, + "required": true }] } ] diff --git a/tests/samples/externals.json b/tests/samples/externals.json index 65119ec..65b2913 100644 --- a/tests/samples/externals.json +++ b/tests/samples/externals.json @@ -1,73 +1,63 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "A", - "namespace": "externals.first", - "fields": [{"name": "a", "type": "boolean"}] + "id": ["externals", "first", "A"], + "kind": "record", + "fields": [{"name": "a", "value": {"kind": "boolean"}, "required": true}] }, { - "type": "record", - "name": "B", - "namespace": "externals.first", - "fields": [{"name": "b", "type": "string"}] + "id": ["externals", "first", "B"], + "kind": "record", + "fields": [{"name": "b", "value": {"kind": "string"}, "required": true}] }, { - "type": "record", - "name": "CC", - "namespace": "externals.first", - "fields": [{"name": "c", "type": "double"}] + "id": ["externals", "first", "CC"], + "kind": "record", + "fields": [{"name": "c", "value": {"kind": "number", "repr": "f64"}, "required": true}] }, { - "type": "record", - "name": "D", - "namespace": "externals.first", - "fields": [{"name": "d", "type": "double"}] + "id": ["externals", "first", "D"], + "kind": "record", + "fields": [{"name": "d", "value": {"kind": "number", "repr": "f64"}, "required": true}] }, { - "type": "record", - "name": "X", - "namespace": "externals", + "id": ["externals", "X"], + "kind": "record", "fields": [ - {"name": "a", "type": "externals.first.A"}, - {"name": "b", "type": "externals.first.B"}, - {"name": "c", "type": "externals.first.CC"}, - {"name": "d", "type": "externals.first.D"} + {"name": "a", "value": {"kind": "reference", "to": ["externals", "first", "A"]}, "required": true}, + {"name": "b", "value": {"kind": "reference", "to": ["externals", "first", "B"]}, "required": true}, + {"name": "c", "value": {"kind": "reference", "to": ["externals", "first", "CC"]}, "required": true}, + {"name": "d", "value": {"kind": "reference", "to": ["externals", "first", "D"]}, "required": true} ] }, { - "type": "record", - "name": "N", - "namespace": "externals.second", - "fields": [{"name": "n", "type": "boolean"}] + "id": ["externals", "second", "N"], + "kind": "record", + "fields": [{"name": "n", "value": {"kind": "boolean"}, "required": true}] }, { - "type": "record", - "name": "M", - "namespace": "externals.second", - "fields": [{"name": "m", "type": "string"}] + "id": ["externals", "second", "M"], + "kind": "record", + "fields": [{"name": "m", "value": {"kind": "string"}, "required": true}] }, { - "type": "record", - "name": "KK", - "namespace": "externals.second", - "fields": [{"name": "k", "type": "double"}] + "id": ["externals", "second", "KK"], + "kind": "record", + "fields": [{"name": "k", "value": {"kind": "number", "repr": "f64"}, "required": true}] }, { - "type": "record", - "name": "P", - "namespace": "externals.second", - "fields": [{"name": "p", "type": "double"}] + "id": ["externals", "second", "P"], + "kind": "record", + "fields": [{"name": "p", "value": {"kind": "number", "repr": "f64"}, "required": true}] }, { - "type": "record", - "name": "Y", - "namespace": "externals", + "id": ["externals", "Y"], + "kind": "record", "fields": [ - {"name": "n", "type": "externals.second.N"}, - {"name": "m", "type": "externals.second.M"}, - {"name": "k", "type": "externals.second.KK"}, - {"name": "p", "type": "externals.second.P"} + {"name": "n", "value": {"kind": "reference", "to": ["externals", "second", "N"]}, "required": true}, + {"name": "m", "value": {"kind": "reference", "to": ["externals", "second", "M"]}, "required": true}, + {"name": "k", "value": {"kind": "reference", "to": ["externals", "second", "KK"]}, "required": true}, + {"name": "p", "value": {"kind": "reference", "to": ["externals", "second", "P"]}, "required": true} ] } ] diff --git a/tests/samples/generics.js b/tests/samples/generics.js index 76a97f6..85b446f 100644 --- a/tests/samples/generics.js +++ b/tests/samples/generics.js @@ -4,7 +4,7 @@ type A = { }; type X = { - a: A, + a: A, }; export {X}; diff --git a/tests/samples/generics.json b/tests/samples/generics.json index fa9cc68..dd1ab4a 100644 --- a/tests/samples/generics.json +++ b/tests/samples/generics.json @@ -1,19 +1,21 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "A__string_double", - "namespace": "generics", + "id": ["generics", "A", "string", "boolean"], + "kind": "record", "fields": [ - {"name": "t", "type": "string"}, - {"name": "k", "type": "double"} + {"name": "t", "value": {"kind": "string"}, "required": true}, + {"name": "k", "value": {"kind": "boolean"}, "required": true} ] }, { - "type": "record", - "name": "X", - "namespace": "generics", - "fields": [{"name": "a", "type": "A__string_double"}] + "id": ["generics", "X"], + "kind": "record", + "fields": [{ + "name": "a", + "value": {"kind": "reference", "to": ["generics", "A", "string", "boolean"]}, + "required": true + }] } ] } diff --git a/tests/samples/inheritance.json b/tests/samples/inheritance.json index 522e057..df40423 100644 --- a/tests/samples/inheritance.json +++ b/tests/samples/inheritance.json @@ -1,53 +1,57 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "A", - "namespace": "inheritance", - "fields": [{"name": "a", "type": "double"}] + "id": ["inheritance", "A"], + "kind": "record", + "fields": [{"name": "a", "value": {"kind": "number", "repr": "f64"}, "required": true}] }, { - "type": "record", - "name": "B", - "namespace": "inheritance", - "fields": [ - {"name": "a", "type": "double"}, - {"name": "b", "type": "string"} + "id": ["inheritance", "B"], + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["inheritance", "A"]}, + { + "kind": "record", + "fields": [{"name": "b", "value": {"kind": "string"}, "required": true}] + } ] }, { - "type": "record", - "name": "C", - "namespace": "inheritance", - "fields": [ - {"name": "a", "type": "double"}, - {"name": "b", "type": "string"}, - {"name": "c", "type": "boolean"} + "id": ["inheritance", "C"], + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["inheritance", "B"]}, + { + "kind": "record", + "fields": [{"name": "c", "value": {"kind": "boolean"}, "required": true}] + } ] }, { - "type": "record", - "name": "X", - "namespace": "inheritance", - "fields": [{"name": "x", "type": "double"}] + "id": ["inheritance", "X"], + "kind": "record", + "fields": [{"name": "x", "value": {"kind": "number", "repr": "f64"}, "required": true}] }, { - "type": "record", - "name": "Y", - "namespace": "inheritance", - "fields": [ - {"name": "x", "type": "double"}, - {"name": "y", "type": "string"} + "id": ["inheritance", "Y"], + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["inheritance", "X"]}, + { + "kind": "record", + "fields": [{"name": "y", "value": {"kind": "string"}, "required": true}] + } ] }, { - "type": "record", - "name": "Z", - "namespace": "inheritance", - "fields": [ - {"name": "x", "type": "double"}, - {"name": "y", "type": "string"}, - {"name": "z", "type": "boolean"} + "id": ["inheritance", "Z"], + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["inheritance", "Y"]}, + { + "kind": "record", + "fields": [{"name": "z", "value": {"kind": "boolean"}, "required": true}] + } ] } ] diff --git a/tests/samples/intersections.json b/tests/samples/intersections.json index 227f2ab..436adb6 100644 --- a/tests/samples/intersections.json +++ b/tests/samples/intersections.json @@ -1,66 +1,64 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "A", - "namespace": "intersections", - "fields": [{"name": "a", "type": "double"}] - }, - { - "type": "record", - "name": "B", - "namespace": "intersections", - "fields": [{"name": "b", "type": "string"}] - }, - { - "type": "record", - "name": "X", - "namespace": "intersections", + "id": ["intersections", "A"], + "kind": "record", "fields": [ - {"name": "a", "type": "double"}, - {"name": "b", "type": "string"} + {"name": "a", "value": {"kind": "number", "repr": "f64"}, "required": true} ] }, { - "type": "record", - "name": "C", - "namespace": "intersections", - "fields": [{"name": "c", "type": "boolean"}] - }, - { - "type": "record", - "name": "_A_B_C", - "namespace": "intersections", + "id": ["intersections", "B"], + "kind": "record", "fields": [ - {"name": "a", "type": "double"}, - {"name": "b", "type": "string"}, - {"name": "c", "type": "boolean"} + {"name": "b", "value": {"kind": "string"}, "required": true} ] }, { - "type": "record", - "name": "Y", - "namespace": "intersections", - "fields": [ - {"name": "y", "type": "_A_B_C"} + "id": ["intersections", "X"], + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["intersections", "A"]}, + {"kind": "reference", "to": ["intersections", "B"]} ] }, { - "type": "record", - "name": "_A_C", - "namespace": "intersections", + "id": ["intersections", "C"], + "kind": "record", "fields": [ - {"name": "a", "type": "double"}, - {"name": "c", "type": "boolean"} + {"name": "c", "value": {"kind": "boolean"}, "required": true} ] }, { - "type": "record", - "name": "Z", - "namespace": "intersections", - "fields": [ - {"name": "z", "type": "_A_C"} - ] + "id": ["intersections", "Y"], + "kind": "record", + "fields": [{ + "name": "y", + "value": { + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["intersections", "A"]}, + {"kind": "reference", "to": ["intersections", "B"]}, + {"kind": "reference", "to": ["intersections", "C"]} + ] + }, + "required": true + }] + }, + { + "id": ["intersections", "Z"], + "kind": "record", + "fields": [{ + "name": "z", + "value": { + "kind": "intersection", + "parts": [ + {"kind": "reference", "to": ["intersections", "A"]}, + {"kind": "reference", "to": ["intersections", "C"]} + ] + }, + "required": true + }] } ] } diff --git a/tests/samples/maps.js b/tests/samples/maps.js index c94b8a1..157c1d0 100644 --- a/tests/samples/maps.js +++ b/tests/samples/maps.js @@ -1,9 +1,9 @@ type Type = { - [string]: number, + [string]: boolean, }; interface Interface { - [string]: number; + [string]: boolean; } export {Type, Interface}; diff --git a/tests/samples/maps.json b/tests/samples/maps.json index 8ff08d2..12c8b8f 100644 --- a/tests/samples/maps.json +++ b/tests/samples/maps.json @@ -1,16 +1,16 @@ { - "schemas": [ + "types": [ { - "type": "map", - "name": "Type", - "namespace": "maps", - "values": "double" + "id": ["maps", "Type"], + "kind": "map", + "keys": {"kind": "string"}, + "values": {"kind": "boolean"} }, { - "type": "map", - "name": "Interface", - "namespace": "maps", - "values": "double" + "id": ["maps", "Interface"], + "kind": "map", + "keys": {"kind": "string"}, + "values": {"kind": "boolean"} } ] } diff --git a/tests/samples/pragmas.js b/tests/samples/pragmas.js index da6ee23..c63e7a8 100644 --- a/tests/samples/pragmas.js +++ b/tests/samples/pragmas.js @@ -1,40 +1,34 @@ type Type = { - // $avro int + // @repr {i32} a: number, - /* $avro long */ + /* @repr {i64} */ b: number, - // $avro float + // @repr {f32} c: number, - // $avro double + // @repr {f64} d: number, - /* $avro fixed 10 */ - e: Buffer, }; interface Interface { - // $avro int + // @repr {i32} a: number; - /* $avro long */ + /* @repr {i64} */ b: number; - // $avro float + // @repr {f32} c: number; - // $avro double + // @repr {f64} d: number; - /* $avro fixed 10 */ - e: Buffer; } class Class { - // $avro int + // @repr {i32} a: number; - /* $avro long */ + /* @repr {i64} */ b: number; - // $avro float + // @repr {f32} c: number; - // $avro double + // @repr {f64} d: number; - /* $avro fixed 10 */ - e: Buffer; } export {Type, Interface, Class}; diff --git a/tests/samples/pragmas.json b/tests/samples/pragmas.json index b7a9400..bde8ca5 100644 --- a/tests/samples/pragmas.json +++ b/tests/samples/pragmas.json @@ -1,39 +1,33 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "pragmas", + "id": ["pragmas", "Type"], + "kind": "record", "fields": [ - {"name": "a", "type": "int"}, - {"name": "b", "type": "long"}, - {"name": "c", "type": "float"}, - {"name": "d", "type": "double"}, - {"name": "e", "type": {"type": "fixed", "size": 10}} + {"name": "a", "value": {"kind": "number", "repr": "i32"}, "required": true}, + {"name": "b", "value": {"kind": "number", "repr": "i64"}, "required": true}, + {"name": "c", "value": {"kind": "number", "repr": "f32"}, "required": true}, + {"name": "d", "value": {"kind": "number", "repr": "f64"}, "required": true} ] }, { - "type": "record", - "name": "Interface", - "namespace": "pragmas", + "id": ["pragmas", "Interface"], + "kind": "record", "fields": [ - {"name": "a", "type": "int"}, - {"name": "b", "type": "long"}, - {"name": "c", "type": "float"}, - {"name": "d", "type": "double"}, - {"name": "e", "type": {"type": "fixed", "size": 10}} + {"name": "a", "value": {"kind": "number", "repr": "i32"}, "required": true}, + {"name": "b", "value": {"kind": "number", "repr": "i64"}, "required": true}, + {"name": "c", "value": {"kind": "number", "repr": "f32"}, "required": true}, + {"name": "d", "value": {"kind": "number", "repr": "f64"}, "required": true} ] }, { - "type": "record", - "name": "Class", - "namespace": "pragmas", + "id": ["pragmas", "Class"], + "kind": "record", "fields": [ - {"name": "a", "type": "int"}, - {"name": "b", "type": "long"}, - {"name": "c", "type": "float"}, - {"name": "d", "type": "double"}, - {"name": "e", "type": {"type": "fixed", "size": 10}} + {"name": "a", "value": {"kind": "number", "repr": "i32"}, "required": true}, + {"name": "b", "value": {"kind": "number", "repr": "i64"}, "required": true}, + {"name": "c", "value": {"kind": "number", "repr": "f32"}, "required": true}, + {"name": "d", "value": {"kind": "number", "repr": "f64"}, "required": true} ] } ] diff --git a/tests/samples/primitives.js b/tests/samples/primitives.js index a2f6823..5960159 100644 --- a/tests/samples/primitives.js +++ b/tests/samples/primitives.js @@ -3,7 +3,6 @@ type Type = { b: number, c: boolean, d: null, - e: Buffer, }; interface Interface { @@ -11,7 +10,6 @@ interface Interface { b: number; c: boolean; d: null; - e: Buffer; }; class Class { @@ -19,7 +17,6 @@ class Class { b: number; c: boolean; d: null; - e: Buffer; } export {Type, Interface, Class}; diff --git a/tests/samples/primitives.json b/tests/samples/primitives.json index 76508f3..d8601c5 100644 --- a/tests/samples/primitives.json +++ b/tests/samples/primitives.json @@ -1,39 +1,33 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "primitives", + "id": ["primitives", "Type"], + "kind": "record", "fields": [ - {"name": "a", "type": "string"}, - {"name": "b", "type": "double"}, - {"name": "c", "type": "boolean"}, - {"name": "d", "type": "null"}, - {"name": "e", "type": "bytes"} + {"name": "a", "value": {"kind": "string"}, "required": true}, + {"name": "b", "value": {"kind": "number", "repr": "f64"}, "required": true}, + {"name": "c", "value": {"kind": "boolean"}, "required": true}, + {"name": "d", "value": {"kind": "literal", "value": null}, "required": true} ] }, { - "type": "record", - "name": "Interface", - "namespace": "primitives", + "id": ["primitives", "Interface"], + "kind": "record", "fields": [ - {"name": "a", "type": "string"}, - {"name": "b", "type": "double"}, - {"name": "c", "type": "boolean"}, - {"name": "d", "type": "null"}, - {"name": "e", "type": "bytes"} + {"name": "a", "value": {"kind": "string"}, "required": true}, + {"name": "b", "value": {"kind": "number", "repr": "f64"}, "required": true}, + {"name": "c", "value": {"kind": "boolean"}, "required": true}, + {"name": "d", "value": {"kind": "literal", "value": null}, "required": true} ] }, { - "type": "record", - "name": "Class", - "namespace": "primitives", + "id": ["primitives", "Class"], + "kind": "record", "fields": [ - {"name": "a", "type": "string"}, - {"name": "b", "type": "double"}, - {"name": "c", "type": "boolean"}, - {"name": "d", "type": "null"}, - {"name": "e", "type": "bytes"} + {"name": "a", "value": {"kind": "string"}, "required": true}, + {"name": "b", "value": {"kind": "number", "repr": "f64"}, "required": true}, + {"name": "c", "value": {"kind": "boolean"}, "required": true}, + {"name": "d", "value": {"kind": "literal", "value": null}, "required": true} ] } ] diff --git a/tests/samples/references.json b/tests/samples/references.json index 68c0938..0816335 100644 --- a/tests/samples/references.json +++ b/tests/samples/references.json @@ -1,35 +1,55 @@ { - "schemas": [ + "types": [ { - "type": "string", - "name": "A", - "namespace": "references" + "id": ["references", "A"], + "kind": "string" }, { - "type": "record", - "name": "Type", - "namespace": "references", + "id": ["references", "Type"], + "kind": "record", "fields": [ - {"name": "a", "type": "A"}, - {"name": "b", "type": {"type": "array", "items": "A"}} + { + "name": "a", + "value": {"kind": "reference", "to": ["references", "A"]}, + "required": true + }, + { + "name": "b", + "value": {"kind": "array", "items": {"kind": "reference", "to": ["references", "A"]}}, + "required": true + } ] }, { - "type": "record", - "name": "Interface", - "namespace": "references", + "id": ["references", "Interface"], + "kind": "record", "fields": [ - {"name": "a", "type": "A"}, - {"name": "b", "type": {"type": "array", "items": "A"}} + { + "name": "a", + "value": {"kind": "reference", "to": ["references", "A"]}, + "required": true + }, + { + "name": "b", + "value": {"kind": "array", "items": {"kind": "reference", "to": ["references", "A"]}}, + "required": true + } ] }, { - "type": "record", - "name": "Class", - "namespace": "references", + "id": ["references", "Class"], + "kind": "record", "fields": [ - {"name": "a", "type": "A"}, - {"name": "b", "type": {"type": "array", "items": "A"}} + { + "name": "a", + "value": {"kind": "reference", "to": ["references", "A"]}, + "required": true + }, + { + "name": "b", + "value": {"kind": "array", "items": {"kind": "reference", "to": ["references", "A"]}}, + "required": true + } ] } ] diff --git a/tests/samples/scopes.json b/tests/samples/scopes.json index 6217781..4f6ca1b 100644 --- a/tests/samples/scopes.json +++ b/tests/samples/scopes.json @@ -1,73 +1,68 @@ { - "schemas": [ + "types": [ { - "type": "boolean", - "name": "X", - "namespace": "scopes._2" + "id": ["scopes", "2", "X"], + "kind": "boolean" }, { - "type": "string", - "name": "Z", - "namespace": "scopes._1" + "id": ["scopes", "1", "Z"], + "kind": "string" }, { - "type": "record", - "name": "Y", - "namespace": "scopes._2", + "id": ["scopes", "2", "Y"], + "kind": "record", "fields": [ - {"name": "x", "type": "X"}, - {"name": "z", "type": "scopes._1.Z"} + {"name": "x", "value": {"kind": "reference", "to": ["scopes", "2", "X"]}, "required": true}, + {"name": "z", "value": {"kind": "reference", "to": ["scopes", "1", "Z"]}, "required": true} ] }, { - "type": "double", - "name": "X", - "namespace": "scopes._3" + "id": ["scopes", "3", "X"], + "kind": "number", + "repr": "f64" }, { - "type": "record", - "name": "Y", - "namespace": "scopes._3", + "id": ["scopes", "3", "Y"], + "kind": "record", "fields": [ - {"name": "x", "type": "X"}, - {"name": "z", "type": "scopes._1.Z"} + {"name": "x", "value": {"kind": "reference", "to": ["scopes", "3", "X"]}, "required": true}, + {"name": "z", "value": {"kind": "reference", "to": ["scopes", "1", "Z"]}, "required": true} ] }, { - "type": "string", - "name": "X", - "namespace": "scopes._4" + "id": ["scopes", "4", "X"], + "kind": "string" }, { - "type": "record", - "name": "Y", - "namespace": "scopes._4", + "id": ["scopes", "4", "Y"], + "kind": "record", "fields": [ - {"name": "x", "type": "X"}, - {"name": "z", "type": "scopes._1.Z"} + {"name": "x", "value": {"kind": "reference", "to": ["scopes", "4", "X"]}, "required": true}, + {"name": "z", "value": {"kind": "reference", "to": ["scopes", "1", "Z"]}, "required": true} ] }, { - "type": "double", - "name": "X", - "namespace": "scopes._1" + "id": ["scopes", "1", "X"], + "kind": "number", + "repr": "f64" }, { - "type": "record", - "name": "Y", - "namespace": "scopes._1", - "fields": [{"name": "x", "type": "X"}] + "id": ["scopes", "1", "Y"], + "kind": "record", + "fields": [ + {"name": "x", "value": {"kind": "reference", "to": ["scopes", "1", "X"]}, "required": true} + ] }, { - "type": "string", - "name": "X", - "namespace": "scopes" + "id": ["scopes", "X"], + "kind": "string" }, { - "type": "record", - "name": "Y", - "namespace": "scopes", - "fields": [{"name": "x", "type": "X"}] + "id": ["scopes", "Y"], + "kind": "record", + "fields": [ + {"name": "x", "value": {"kind": "reference", "to": ["scopes", "X"]}, "required": true} + ] } ] } diff --git a/tests/samples/shadowing.json b/tests/samples/shadowing.json index 38a278f..6911372 100644 --- a/tests/samples/shadowing.json +++ b/tests/samples/shadowing.json @@ -1,25 +1,30 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Buffer", - "namespace": "shadowing._1", + "id": ["shadowing", "1", "Buffer"], + "kind": "record", "fields": [] }, { - "type": "record", - "name": "Y", - "namespace": "shadowing._1", + "id": ["shadowing", "1", "Y"], + "kind": "record", "fields": [ - {"name": "y", "type": "Buffer"} + { + "name": "y", + "value": {"kind": "reference", "to": ["shadowing", "1", "Buffer"]}, + "required": true + } ] }, { - "type": "record", - "name": "X", - "namespace": "shadowing", + "id": ["shadowing", "X"], + "kind": "record", "fields": [ - {"name": "x", "type": "bytes"} + { + "name": "x", + "value": {"kind": "reference", "to": ["Buffer"]}, + "required": true + } ] } ] diff --git a/tests/samples/skipFunctions.js b/tests/samples/skipFunctions.js index cf91c84..d124cdf 100644 --- a/tests/samples/skipFunctions.js +++ b/tests/samples/skipFunctions.js @@ -3,7 +3,7 @@ type Type = { foo(): void, - b: number, + b: boolean, bar: () => void, }; @@ -13,7 +13,7 @@ interface Interface { foo(): void; - b: number; + b: boolean; bar: () => void; } @@ -25,7 +25,7 @@ class Class { get bar() {} set bar(a) {} - b: number; + b: boolean; baz: () => void; } diff --git a/tests/samples/skipFunctions.json b/tests/samples/skipFunctions.json index 95724a0..fa4ebfc 100644 --- a/tests/samples/skipFunctions.json +++ b/tests/samples/skipFunctions.json @@ -1,30 +1,27 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "skipFunctions", + "id": ["skipFunctions", "Type"], + "kind": "record", "fields": [ - {"name": "a", "type": "string"}, - {"name": "b", "type": "double"} + {"name": "a", "value": {"kind": "string"}, "required": true}, + {"name": "b", "value": {"kind": "boolean"}, "required": true} ] }, { - "type": "record", - "name": "Interface", - "namespace": "skipFunctions", + "id": ["skipFunctions", "Interface"], + "kind": "record", "fields": [ - {"name": "a", "type": "string"}, - {"name": "b", "type": "double"} + {"name": "a", "value": {"kind": "string"}, "required": true}, + {"name": "b", "value": {"kind": "boolean"}, "required": true} ] }, { - "type": "record", - "name": "Class", - "namespace": "skipFunctions", + "id": ["skipFunctions", "Class"], + "kind": "record", "fields": [ - {"name": "a", "type": "string"}, - {"name": "b", "type": "double"} + {"name": "a", "value": {"kind": "string"}, "required": true}, + {"name": "b", "value": {"kind": "boolean"}, "required": true} ] } ] diff --git a/tests/samples/typeInMethod.json b/tests/samples/typeInMethod.json index cd39e70..ce943dc 100644 --- a/tests/samples/typeInMethod.json +++ b/tests/samples/typeInMethod.json @@ -1,16 +1,18 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Test", - "namespace": "typeInMethod", + "id": ["typeInMethod", "Test"], + "kind": "record", "fields": [] }, { - "type": "record", - "name": "X", - "namespace": "typeInMethod._1", - "fields": [{"name": "t", "type": "typeInMethod.Test"}] + "id": ["typeInMethod", "1", "X"], + "kind": "record", + "fields": [{ + "name": "t", + "value": {"kind": "reference", "to": ["typeInMethod", "Test"]}, + "required": true + }] } ] } diff --git a/tests/samples/unions.js b/tests/samples/unions.js index e7b0b54..d8b4236 100644 --- a/tests/samples/unions.js +++ b/tests/samples/unions.js @@ -1,16 +1,13 @@ type Type = { - a: string | number, - b: ?string, + a: string | boolean, }; interface Interface { - a: string | number; - b: ?string; + a: string | boolean | number; } class Class { - a: string | number; - b: ?string; + a: string | boolean; } export {Type, Interface, Class}; diff --git a/tests/samples/unions.json b/tests/samples/unions.json index 052565c..7b00130 100644 --- a/tests/samples/unions.json +++ b/tests/samples/unions.json @@ -1,47 +1,44 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "unions", + "id": ["unions", "Type"], + "kind": "record", "fields": [ { "name": "a", - "type": ["string", "double"] - }, - { - "name": "b", - "type": ["null", "string"] + "value": { + "kind": "union", + "variants": [{"kind": "string"}, {"kind": "boolean"}] + }, + "required": true } ] }, { - "type": "record", - "name": "Interface", - "namespace": "unions", + "id": ["unions", "Interface"], + "kind": "record", "fields": [ { "name": "a", - "type": ["string", "double"] - }, - { - "name": "b", - "type": ["null", "string"] + "value": { + "kind": "union", + "variants": [{"kind": "string"}, {"kind": "boolean"}, {"kind": "number", "repr": "f64"}] + }, + "required": true } ] }, { - "type": "record", - "name": "Class", - "namespace": "unions", + "id": ["unions", "Class"], + "kind": "record", "fields": [ { "name": "a", - "type": ["string", "double"] - }, - { - "name": "b", - "type": ["null", "string"] + "value": { + "kind": "union", + "variants": [{"kind": "string"}, {"kind": "boolean"}] + }, + "required": true } ] } diff --git a/tests/samples/unionsAndEnums.js b/tests/samples/unionsAndEnums.js index 160f44c..340542b 100644 --- a/tests/samples/unionsAndEnums.js +++ b/tests/samples/unionsAndEnums.js @@ -1,13 +1,13 @@ type Type = { - a: 'one' | 'two' | number, + a: 'one' | 'two' | string, }; interface Interface { - a: 'one' | 'two' | number; + a: 'one' | 'two' | string; } class Class { - a: 'one' | 'two' | number; + a: 'one' | 'two' | string; } export {Type, Interface, Class}; diff --git a/tests/samples/unionsAndEnums.json b/tests/samples/unionsAndEnums.json index 0190fa1..2b15fc6 100644 --- a/tests/samples/unionsAndEnums.json +++ b/tests/samples/unionsAndEnums.json @@ -1,39 +1,51 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "unionsAndEnums", + "id": ["unionsAndEnums", "Type"], + "kind": "record", "fields": [{ "name": "a", - "type": [ - "double", - {"type": "enum", "symbols": ["one", "two"]} - ] + "value": { + "kind": "union", + "variants": [ + {"kind": "literal", "value": "one"}, + {"kind": "literal", "value": "two"}, + {"kind": "string"} + ] + }, + "required": true }] }, { - "type": "record", - "name": "Interface", - "namespace": "unionsAndEnums", + "id": ["unionsAndEnums", "Interface"], + "kind": "record", "fields": [{ "name": "a", - "type": [ - "double", - {"type": "enum", "symbols": ["one", "two"]} - ] + "value": { + "kind": "union", + "variants": [ + {"kind": "literal", "value": "one"}, + {"kind": "literal", "value": "two"}, + {"kind": "string"} + ] + }, + "required": true }] }, { - "type": "record", - "name": "Class", - "namespace": "unionsAndEnums", + "id": ["unionsAndEnums", "Class"], + "kind": "record", "fields": [{ "name": "a", - "type": [ - "double", - {"type": "enum", "symbols": ["one", "two"]} - ] + "value": { + "kind": "union", + "variants": [ + {"kind": "literal", "value": "one"}, + {"kind": "literal", "value": "two"}, + {"kind": "string"} + ] + }, + "required": true }] } ] diff --git a/tests/samples/unused.json b/tests/samples/unused.json index 26692e6..e0d4d26 100644 --- a/tests/samples/unused.json +++ b/tests/samples/unused.json @@ -1,3 +1,3 @@ { - "schemas": [] + "types": [] } diff --git a/tests/samples/valueAsType.json b/tests/samples/valueAsType.json index 64ed936..9b818ba 100644 --- a/tests/samples/valueAsType.json +++ b/tests/samples/valueAsType.json @@ -1,27 +1,24 @@ { - "schemas": [ + "types": [ { - "type": "record", - "name": "Type", - "namespace": "valueAsType", + "id": ["valueAsType", "Type"], + "kind": "record", "fields": [ - {"name": "a", "type": {"type": "enum", "symbols": ["one"]}} + {"name": "a", "value": {"kind": "literal", "value": "one"}, "required": true} ] }, { - "type": "record", - "name": "Interface", - "namespace": "valueAsType", + "id": ["valueAsType", "Interface"], + "kind": "record", "fields": [ - {"name": "a", "type": {"type": "enum", "symbols": ["one"]}} + {"name": "a", "value": {"kind": "literal", "value": "one"}, "required": true} ] }, { - "type": "record", - "name": "Class", - "namespace": "valueAsType", + "id": ["valueAsType", "Class"], + "kind": "record", "fields": [ - {"name": "a", "type": {"type": "enum", "symbols": ["one"]}} + {"name": "a", "value": {"kind": "literal", "value": "one"}, "required": true} ] } ]