Use flow to check code
parent
29ec78a994
commit
94535791e9
1
.babelrc
1
.babelrc
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"presets": [
|
"presets": [
|
||||||
|
"@babel/flow",
|
||||||
["@babel/env", {
|
["@babel/env", {
|
||||||
"targets": {
|
"targets": {
|
||||||
"node": "6.10"
|
"node": "6.10"
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
[ignore]
|
||||||
|
.*/node_modules/.*
|
||||||
|
.*/tests/samples/.*
|
||||||
|
.*/src/extractors.js
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
declarations/
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
all=warn
|
||||||
|
|
||||||
|
[options]
|
||||||
|
all=true
|
||||||
|
module.use_strict=true
|
||||||
|
munge_underscores=true
|
||||||
|
include_warnings=true
|
||||||
|
unsafe.enable_getters_and_setters=true
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
||||||
|
declare module 'json-stringify-pretty-compact' {
|
||||||
|
declare export default (any, ?{maxLength?: number}) => string;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
declare function describe(string, Function): void;
|
||||||
|
declare function it(string, Function): void;
|
||||||
|
declare function before(Function): void;
|
|
@ -0,0 +1,11 @@
|
||||||
|
declare module 'optimist' {
|
||||||
|
declare type Optimist = {
|
||||||
|
usage(string): Optimist,
|
||||||
|
argv: {
|
||||||
|
[string]: any,
|
||||||
|
_: string[],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module.exports: Optimist;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
declare module 'resolve' {
|
||||||
|
declare function sync(string, ?{basedir: string}): string;
|
||||||
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
"flow2schema": "./bin/flow2schema"
|
"flow2schema": "./bin/flow2schema"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.0.0-beta.32",
|
||||||
"babylon": "^7.0.0-beta.32",
|
"babylon": "^7.0.0-beta.32",
|
||||||
"json-stringify-pretty-compact": "^1.0.4",
|
"json-stringify-pretty-compact": "^1.0.4",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
|
@ -32,7 +33,9 @@
|
||||||
"@babel/cli": "^7.0.0-beta.32",
|
"@babel/cli": "^7.0.0-beta.32",
|
||||||
"@babel/core": "^7.0.0-beta.32",
|
"@babel/core": "^7.0.0-beta.32",
|
||||||
"@babel/preset-env": "^7.0.0-beta.32",
|
"@babel/preset-env": "^7.0.0-beta.32",
|
||||||
|
"@babel/preset-flow": "^7.0.0-beta.32",
|
||||||
"@babel/register": "^7.0.0-beta.32",
|
"@babel/register": "^7.0.0-beta.32",
|
||||||
|
"flow-bin": "^0.59.0",
|
||||||
"jasmine": "^2.8.0",
|
"jasmine": "^2.8.0",
|
||||||
"mocha": "^4.0.1",
|
"mocha": "^4.0.1",
|
||||||
"nyc": "^11.3.0"
|
"nyc": "^11.3.0"
|
||||||
|
@ -40,6 +43,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"build": "babel src/ -d lib/",
|
"build": "babel src/ -d lib/",
|
||||||
"test": "nyc mocha -r @babel/register -R list tests/run.js"
|
"test": "flow && nyc mocha -r @babel/register -R list tests/run.js",
|
||||||
|
"flow": "flow"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const argv = optimist
|
||||||
|
|
||||||
argv._.forEach(run);
|
argv._.forEach(run);
|
||||||
|
|
||||||
function run(path) {
|
function run(path: string) {
|
||||||
if (path === '-') {
|
if (path === '-') {
|
||||||
path = '/dev/stdin';
|
path = '/dev/stdin';
|
||||||
}
|
}
|
||||||
|
|
245
src/collector.js
245
src/collector.js
|
@ -1,34 +1,60 @@
|
||||||
import * as assert from 'assert';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as pathlib from 'path';
|
import * as pathlib from 'path';
|
||||||
|
import type {Node} from '@babel/types';
|
||||||
|
|
||||||
import globals from './globals';
|
import globals from './globals';
|
||||||
|
// $FlowFixMe
|
||||||
import * as extractors from './extractors';
|
import * as extractors from './extractors';
|
||||||
import Command from './commands';
|
|
||||||
import Module from './module';
|
import Module from './module';
|
||||||
import Scope from './scope';
|
import Scope from './scope';
|
||||||
import CircularList from './list';
|
import CircularList from './list';
|
||||||
import {isNode} from './utils';
|
import {invariant, isNode} from './utils';
|
||||||
|
import type Parser from './parser';
|
||||||
|
import type {Schema} from './schema';
|
||||||
|
|
||||||
|
type Task = Generator<void, ?Schema, void>;
|
||||||
|
|
||||||
|
type Group = {
|
||||||
|
entries: string[],
|
||||||
|
|
||||||
|
[string]: Node => Generator<any, any, any>,
|
||||||
|
};
|
||||||
|
|
||||||
|
type InstanceParam = {
|
||||||
|
name: string,
|
||||||
|
value: Schema,
|
||||||
|
};
|
||||||
|
|
||||||
export default class Collector {
|
export default class Collector {
|
||||||
constructor(parser, root = '.') {
|
+root: string;
|
||||||
|
+parser: Parser;
|
||||||
|
+schemas: Schema[];
|
||||||
|
taskCount: number;
|
||||||
|
_tasks: CircularList<Task>;
|
||||||
|
_active: boolean;
|
||||||
|
_modules: Map<string, Module>;
|
||||||
|
_roots: Set<Node>;
|
||||||
|
_global: Scope;
|
||||||
|
_running: boolean;
|
||||||
|
|
||||||
|
constructor(parser: Parser, root: string = '.') {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
this.schemas = [];
|
this.schemas = [];
|
||||||
this.tasks = new CircularList;
|
|
||||||
this.taskCount = 0;
|
this.taskCount = 0;
|
||||||
this.active = true;
|
this._tasks = new CircularList;
|
||||||
this.modules = new Map;
|
this._active = true;
|
||||||
this.roots = new Set;
|
this._modules = new Map;
|
||||||
this.global = Scope.global(globals);
|
this._roots = new Set;
|
||||||
this.running = false;
|
this._global = Scope.global(globals);
|
||||||
|
this._running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
collect(path, internal = false) {
|
collect(path: string, internal: boolean = false) {
|
||||||
// TODO: follow symlinks.
|
// TODO: follow symlinks.
|
||||||
path = pathlib.resolve(path);
|
path = pathlib.resolve(path);
|
||||||
|
|
||||||
let module = this.modules.get(path);
|
let module = this._modules.get(path);
|
||||||
|
|
||||||
if (module) {
|
if (module) {
|
||||||
return;
|
return;
|
||||||
|
@ -43,18 +69,18 @@ export default class Collector {
|
||||||
|
|
||||||
module = new Module(path, namespace);
|
module = new Module(path, namespace);
|
||||||
|
|
||||||
const scope = this.global.extend(module);
|
const scope = this._global.extend(module);
|
||||||
|
|
||||||
this._freestyle(extractors.declaration, ast.program, scope, null);
|
this._freestyle(extractors.declaration, ast.program, scope, []);
|
||||||
|
|
||||||
this.modules.set(path, module);
|
this._modules.set(path, module);
|
||||||
|
|
||||||
if (this.running) {
|
if (this._running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.running = true;
|
this._running = true;
|
||||||
this._schedule();
|
this._schedule();
|
||||||
|
|
||||||
if (!internal) {
|
if (!internal) {
|
||||||
|
@ -63,12 +89,12 @@ export default class Collector {
|
||||||
this._schedule();
|
this._schedule();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.running = false;
|
this._running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given the AST output of babylon parse, walk through in a depth-first order.
|
// Given the AST output of babylon parse, walk through in a depth-first order.
|
||||||
_freestyle(group, root, scope, params) {
|
_freestyle(group: Group, root: Node, scope: Scope, params: InstanceParam[]) {
|
||||||
let stack;
|
let stack;
|
||||||
let parent;
|
let parent;
|
||||||
let keys = [];
|
let keys = [];
|
||||||
|
@ -86,12 +112,13 @@ export default class Collector {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
const node = parent ? parent[keys[index]] : root;
|
const node = parent ? parent[keys[index]] : root;
|
||||||
|
|
||||||
if (isNode(node) && isAcceptableGroup(group, node)) {
|
if (isNode(node) && isAcceptableGroup(group, node)) {
|
||||||
if (!this.roots.has(node)) {
|
if (!this._roots.has(node)) {
|
||||||
const task = this._collect(group, node, scope, params);
|
const task = this._collect(group, node, scope, params);
|
||||||
this.roots.add(node);
|
this._roots.add(node);
|
||||||
this._spawn(task);
|
this._spawn(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,11 +134,12 @@ export default class Collector {
|
||||||
} while (stack);
|
} while (stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
* _collect(group, node, scope, params) {
|
* _collect(group: Group, node: Node, scope: Scope, params: InstanceParam[]): Task {
|
||||||
|
// $FlowFixMe
|
||||||
const extractor = group[node.type];
|
const extractor = group[node.type];
|
||||||
|
|
||||||
if (!extractor) {
|
if (!extractor) {
|
||||||
this._freestyle(group, node, scope, null);
|
this._freestyle(group, node, scope, []);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +148,7 @@ export default class Collector {
|
||||||
let result = null;
|
let result = null;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
this.active = true;
|
this._active = true;
|
||||||
|
|
||||||
const {done, value} = iter.next(result);
|
const {done, value} = iter.next(result);
|
||||||
|
|
||||||
|
@ -128,83 +156,78 @@ export default class Collector {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(value);
|
invariant(value);
|
||||||
|
|
||||||
if (value instanceof Command) {
|
if (isNode(value)) {
|
||||||
switch (value.name) {
|
result = yield* this._collect(group, value, scope, params);
|
||||||
case 'declare':
|
|
||||||
scope.addDeclaration(...value.data);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'define':
|
|
||||||
const [schema, declared] = value.data;
|
|
||||||
|
|
||||||
if (declared && params) {
|
|
||||||
const name = schema.name;
|
|
||||||
|
|
||||||
schema.name = generateGenericName(name, params);
|
|
||||||
|
|
||||||
scope.addInstance(name, schema, params.map(p => p.value));
|
|
||||||
} else {
|
|
||||||
scope.addDefinition(...value.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.schemas.push(schema);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'external':
|
|
||||||
scope.addImport(value.data);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'provide':
|
|
||||||
scope.addExport(...value.data);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'query':
|
|
||||||
if (params) {
|
|
||||||
const param = params.find(p => p.name === value.data[0]);
|
|
||||||
|
|
||||||
if (param) {
|
|
||||||
result = param.value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = yield* this._query(scope, ...value.data);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'enter':
|
|
||||||
scope = scope.extend();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'exit':
|
|
||||||
assert.ok(scope.parent);
|
|
||||||
scope = scope.parent;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'namespace':
|
|
||||||
result = scope.namespace;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
result = [];
|
result = [];
|
||||||
|
|
||||||
for (const val of value) {
|
for (const val of value) {
|
||||||
result.push(yield* this._collect(group, val, scope, params));
|
result.push(yield* this._collect(group, val, scope, params));
|
||||||
}
|
}
|
||||||
} else {
|
} else switch (value.kind) {
|
||||||
assert.ok(isNode(value));
|
case 'declare':
|
||||||
result = yield* this._collect(group, value, scope, params);
|
scope.addDeclaration(value.name, value.node, value.params);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'define':
|
||||||
|
const {schema, declared} = value;
|
||||||
|
|
||||||
|
if (declared && params.length > 0) {
|
||||||
|
const name = schema.name;
|
||||||
|
|
||||||
|
schema.name = generateGenericName(name, params);
|
||||||
|
|
||||||
|
scope.addInstance(name, schema, params.map(p => p.value));
|
||||||
|
} else {
|
||||||
|
scope.addDefinition(schema, declared);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schemas.push(schema);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'external':
|
||||||
|
scope.addImport(value.external);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'provide':
|
||||||
|
scope.addExport(value.name, value.reference);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'query':
|
||||||
|
const param = params.find(p => p.name === value.name);
|
||||||
|
|
||||||
|
if (param) {
|
||||||
|
// TODO: warning about missing param.
|
||||||
|
result = param.value;
|
||||||
|
} else {
|
||||||
|
result = yield* this._query(scope, value.name, value.params);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'enter':
|
||||||
|
scope = scope.extend();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'exit':
|
||||||
|
invariant(scope.parent);
|
||||||
|
scope = scope.parent;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'namespace':
|
||||||
|
result = scope.namespace;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* _query(scope, name, params) {
|
* _query(scope: Scope, name: string, params: Schema[]): Task {
|
||||||
let result = scope.query(name, params);
|
let result = scope.query(name, params);
|
||||||
|
|
||||||
// TODO: warning.
|
// TODO: warning.
|
||||||
assert.notEqual(result.type, 'unknown');
|
invariant(result.type !== 'unknown');
|
||||||
|
|
||||||
// Resulting scope is always the best choice for waiting.
|
// Resulting scope is always the best choice for waiting.
|
||||||
scope = result.scope;
|
scope = result.scope;
|
||||||
|
@ -218,7 +241,10 @@ export default class Collector {
|
||||||
|
|
||||||
this.collect(modulePath, true);
|
this.collect(modulePath, true);
|
||||||
|
|
||||||
const module = this.modules.get(modulePath);
|
const module = this._modules.get(modulePath);
|
||||||
|
|
||||||
|
invariant(module);
|
||||||
|
|
||||||
const {imported} = result.info;
|
const {imported} = result.info;
|
||||||
|
|
||||||
while ((result = module.query(imported, params)).type === 'unknown') {
|
while ((result = module.query(imported, params)).type === 'unknown') {
|
||||||
|
@ -230,7 +256,7 @@ export default class Collector {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: reexports.
|
// TODO: reexports.
|
||||||
assert.ok(result.type === 'declaration' || result.type === 'template');
|
invariant(result.type === 'declaration' || result.type === 'template');
|
||||||
|
|
||||||
scope = result.scope;
|
scope = result.scope;
|
||||||
name = result.name;
|
name = result.name;
|
||||||
|
@ -238,23 +264,27 @@ export default class Collector {
|
||||||
// Fallthrough.
|
// Fallthrough.
|
||||||
case 'declaration':
|
case 'declaration':
|
||||||
case 'template':
|
case 'template':
|
||||||
let tmplParams = null;
|
const tmplParams = [];
|
||||||
|
|
||||||
if (result.type === 'template') {
|
if (result.type === 'template') {
|
||||||
tmplParams = result.params.map((p, i) => ({
|
for (const [i, p] of result.params.entries()) {
|
||||||
name: p.name,
|
tmplParams.push({
|
||||||
value: params[i] || p.default,
|
name: p.name,
|
||||||
}));
|
value: params[i] || p.default,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invariant(result.type === 'declaration' || result.type === 'template');
|
||||||
|
|
||||||
this._freestyle(extractors.definition, result.node, scope, tmplParams);
|
this._freestyle(extractors.definition, result.node, scope, tmplParams);
|
||||||
|
|
||||||
while ((result = scope.query(name, params)).type !== 'definition') {
|
while ((result = scope.query(name, params)).type !== 'definition') {
|
||||||
assert.notEqual(result.type, 'external');
|
invariant(result.type !== 'external');
|
||||||
yield;
|
yield;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.equal(result.type, 'definition');
|
invariant(result.type === 'definition');
|
||||||
|
|
||||||
// Fallthrough.
|
// Fallthrough.
|
||||||
case 'definition':
|
case 'definition':
|
||||||
|
@ -262,19 +292,19 @@ export default class Collector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* _grabExports(module) {
|
* _grabExports(module: Module): Task {
|
||||||
for (const [scope, name] of module.exports()) {
|
for (const [scope, name] of module.exports()) {
|
||||||
yield* this._query(scope, name, null);
|
yield* this._query(scope, name, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_spawn(task) {
|
_spawn(task: Task) {
|
||||||
this.tasks.add(task);
|
this._tasks.add(task);
|
||||||
++this.taskCount;
|
++this.taskCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_schedule() {
|
_schedule() {
|
||||||
const {tasks} = this;
|
const tasks = this._tasks;
|
||||||
|
|
||||||
let marker = null;
|
let marker = null;
|
||||||
|
|
||||||
|
@ -290,9 +320,9 @@ export default class Collector {
|
||||||
|
|
||||||
tasks.add(task);
|
tasks.add(task);
|
||||||
|
|
||||||
if (this.active) {
|
if (this._active) {
|
||||||
marker = task;
|
marker = task;
|
||||||
this.active = false;
|
this._active = false;
|
||||||
} else if (task === marker) {
|
} else if (task === marker) {
|
||||||
// TODO: warning.
|
// TODO: warning.
|
||||||
return;
|
return;
|
||||||
|
@ -301,7 +331,7 @@ export default class Collector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pathToNamespace(path) {
|
function pathToNamespace(path: string): string {
|
||||||
const pathObj = pathlib.parse(path);
|
const pathObj = pathlib.parse(path);
|
||||||
|
|
||||||
return pathlib.format({
|
return pathlib.format({
|
||||||
|
@ -313,15 +343,16 @@ function pathToNamespace(path) {
|
||||||
.join('.');
|
.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAcceptableGroup(group, node) {
|
function isAcceptableGroup(group: Group, node: Node): boolean {
|
||||||
|
// $FlowFixMe
|
||||||
return group.entries.includes(node.type);
|
return group.entries.includes(node.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateGenericName(base, params) {
|
function generateGenericName(base: string, params: InstanceParam[]): string {
|
||||||
let name = base + '_';
|
let name = base + '_';
|
||||||
|
|
||||||
for (const {value} of params) {
|
for (const {value} of params) {
|
||||||
assert.equal(typeof value, 'string');
|
invariant(typeof value === 'string');
|
||||||
name += '_' + value;
|
name += '_' + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,66 @@
|
||||||
export default class Command {
|
import type {Node} from '@babel/types';
|
||||||
constructor(name, data) {
|
|
||||||
this.name = name;
|
import type {Schema} from './schema';
|
||||||
this.data = data;
|
import type {TemplateParam, ExternalInfo} from './query';
|
||||||
}
|
|
||||||
|
export type Command =
|
||||||
|
| {kind: 'declare', name: string, node: Node, params: TemplateParam[]}
|
||||||
|
| {kind: 'define', schema: Schema, declared: boolean}
|
||||||
|
| {kind: 'external', external: ExternalInfo}
|
||||||
|
| {kind: 'provide', name: string, reference: string}
|
||||||
|
| {kind: 'query', name: string, params: Schema[]}
|
||||||
|
| {kind: 'enter'}
|
||||||
|
| {kind: 'exit'}
|
||||||
|
| {kind: 'namespace'};
|
||||||
|
|
||||||
|
export function declare(name: string, node: Node, params: ?TemplateParam[]): Command {
|
||||||
|
return {
|
||||||
|
kind: 'declare',
|
||||||
|
name,
|
||||||
|
node,
|
||||||
|
params: params || [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function declare(name, node, params) {
|
export function define(schema: Schema, declared: boolean = true): Command {
|
||||||
return new Command('declare', [name, node, params]);
|
return {
|
||||||
|
kind: 'define',
|
||||||
|
schema,
|
||||||
|
declared,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function define(schema, declared = true) {
|
export function external(external: ExternalInfo): Command {
|
||||||
return new Command('define', [schema, declared]);
|
return {
|
||||||
|
kind: 'external',
|
||||||
|
external,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function external(external) {
|
export function provide(name: string, reference: string = name): Command {
|
||||||
return new Command('external', external);
|
return {
|
||||||
|
kind: 'provide',
|
||||||
|
name,
|
||||||
|
reference,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function provide(name, reference = name) {
|
export function query(name: string, params: ?Schema[]): Command {
|
||||||
return new Command('provide', [name, reference]);
|
return {
|
||||||
|
kind: 'query',
|
||||||
|
name,
|
||||||
|
params: params || [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function query(name, params = null) {
|
export function enter(): Command {
|
||||||
return new Command('query', [name, params]);
|
return {kind: 'enter'};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enter() {
|
export function exit(): Command {
|
||||||
return new Command('enter');
|
return {kind: 'exit'};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exit() {
|
export function namespace(): Command {
|
||||||
return new Command('exit');
|
return {kind: 'namespace'};
|
||||||
}
|
|
||||||
|
|
||||||
export function namespace() {
|
|
||||||
return new Command('namespace');
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import * as assert from 'assert';
|
import * as t from '@babel/types';
|
||||||
|
|
||||||
import {declare, define, external, provide, query, enter, exit, namespace} from './commands';
|
import {declare, define, external, provide, query, enter, exit, namespace} from './commands';
|
||||||
import {partition, isNode} from './utils';
|
import {invariant, partition} from './utils';
|
||||||
|
import type {Schema} from './schema';
|
||||||
|
|
||||||
|
type E = Generator<any, any, any>;
|
||||||
|
|
||||||
export const definition = {
|
export const definition = {
|
||||||
entries: [
|
entries: [
|
||||||
|
@ -10,7 +13,7 @@ export const definition = {
|
||||||
'ClassDeclaration',
|
'ClassDeclaration',
|
||||||
],
|
],
|
||||||
|
|
||||||
* TypeAlias(node) {
|
* TypeAlias(node: t.TypeAlias): E {
|
||||||
let schema = yield node.right;
|
let schema = yield node.right;
|
||||||
|
|
||||||
if (typeof schema === 'string') {
|
if (typeof schema === 'string') {
|
||||||
|
@ -25,7 +28,7 @@ export const definition = {
|
||||||
return schema;
|
return schema;
|
||||||
},
|
},
|
||||||
|
|
||||||
* InterfaceDeclaration(node) {
|
* InterfaceDeclaration(node: t.InterfaceDeclaration): E {
|
||||||
let schema = yield node.body;
|
let schema = yield node.body;
|
||||||
|
|
||||||
if (node.extends.length > 0) {
|
if (node.extends.length > 0) {
|
||||||
|
@ -51,7 +54,7 @@ export const definition = {
|
||||||
return schema;
|
return schema;
|
||||||
},
|
},
|
||||||
|
|
||||||
* ClassDeclaration(node) {
|
* ClassDeclaration(node: t.ClassDeclaration): E {
|
||||||
let schema = yield node.body;
|
let schema = yield node.body;
|
||||||
|
|
||||||
if (node.superClass) {
|
if (node.superClass) {
|
||||||
|
@ -69,14 +72,14 @@ export const definition = {
|
||||||
return schema;
|
return schema;
|
||||||
},
|
},
|
||||||
|
|
||||||
* ClassBody(node) {
|
* ClassBody(node: t.ClassBody): E {
|
||||||
return {
|
return {
|
||||||
type: 'record',
|
type: 'record',
|
||||||
fields: (yield node.body).filter(Boolean),
|
fields: (yield node.body).filter(Boolean),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
* ClassProperty(node) {
|
* ClassProperty(node: t.ClassProperty): E {
|
||||||
if (node.static) {
|
if (node.static) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -84,17 +87,17 @@ export const definition = {
|
||||||
return yield* extractProperty(node, node.typeAnnotation);
|
return yield* extractProperty(node, node.typeAnnotation);
|
||||||
},
|
},
|
||||||
|
|
||||||
* ClassMethod(node) {
|
* ClassMethod(node: t.ClassMethod): E {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
* ObjectTypeAnnotation(node) {
|
* ObjectTypeAnnotation(node: t.ObjectTypeAnnotation): E {
|
||||||
if (node.indexers.length > 0) {
|
if (node.indexers.length > 0) {
|
||||||
// Allow functions, getters and setters.
|
// Allow functions, getters and setters.
|
||||||
const properties = (yield node.properties).filter(Boolean);
|
const properties = (yield node.properties).filter(Boolean);
|
||||||
|
|
||||||
assert.equal(properties.length, 0);
|
invariant(properties.length === 0);
|
||||||
assert.equal(node.indexers.length, 1);
|
invariant(node.indexers.length === 1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'map',
|
type: 'map',
|
||||||
|
@ -108,42 +111,42 @@ export const definition = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
* ObjectTypeProperty(node) {
|
* ObjectTypeProperty(node: t.ObjectTypeProperty): E {
|
||||||
return yield* extractProperty(node, node.value);
|
return yield* extractProperty(node, node.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
* ObjectTypeIndexer(node) {
|
* ObjectTypeIndexer(node: t.ObjectTypeIndexer): E {
|
||||||
const key = yield node.key;
|
const key = yield node.key;
|
||||||
|
|
||||||
assert.equal(key, 'string');
|
invariant(key === 'string');
|
||||||
|
|
||||||
return yield node.value;
|
return yield node.value;
|
||||||
},
|
},
|
||||||
|
|
||||||
* TypeAnnotation(node) {
|
* TypeAnnotation(node: t.TypeAnnotation): E {
|
||||||
return yield node.typeAnnotation;
|
return yield node.typeAnnotation;
|
||||||
},
|
},
|
||||||
|
|
||||||
* NumberTypeAnnotation(node) {
|
* NumberTypeAnnotation(node: t.NumberTypeAnnotation): E {
|
||||||
return 'double';
|
return 'double';
|
||||||
},
|
},
|
||||||
|
|
||||||
* StringTypeAnnotation(node) {
|
* StringTypeAnnotation(node: t.StringTypeAnnotation): E {
|
||||||
return 'string';
|
return 'string';
|
||||||
},
|
},
|
||||||
|
|
||||||
* BooleanTypeAnnotation(node) {
|
* BooleanTypeAnnotation(node: t.BooleanTypeAnnotation): E {
|
||||||
return 'boolean';
|
return 'boolean';
|
||||||
},
|
},
|
||||||
|
|
||||||
* ArrayTypeAnnotation(node) {
|
* ArrayTypeAnnotation(node: t.ArrayTypeAnnotation): E {
|
||||||
return {
|
return {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: yield node.elementType,
|
items: yield node.elementType,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
* UnionTypeAnnotation(node) {
|
* UnionTypeAnnotation(node: t.UnionTypeAnnotation): E {
|
||||||
// TODO: flatten variants.
|
// TODO: flatten variants.
|
||||||
|
|
||||||
let [symbols, variants] = partition(node.types, isEnumSymbol);
|
let [symbols, variants] = partition(node.types, isEnumSymbol);
|
||||||
|
@ -167,10 +170,11 @@ export const definition = {
|
||||||
return variants;
|
return variants;
|
||||||
},
|
},
|
||||||
|
|
||||||
* IntersectionTypeAnnotation(node) {
|
* IntersectionTypeAnnotation(node: t.IntersectionTypeAnnotation): E {
|
||||||
const schemas = [];
|
const schemas = [];
|
||||||
|
|
||||||
for (const type of node.types) {
|
for (const type of node.types) {
|
||||||
|
// TODO: support arbitrary types, not only references.
|
||||||
const name = yield type;
|
const name = yield type;
|
||||||
const schema = yield query(name);
|
const schema = yield query(name);
|
||||||
|
|
||||||
|
@ -180,22 +184,22 @@ export const definition = {
|
||||||
return mergeSchemas(schemas);
|
return mergeSchemas(schemas);
|
||||||
},
|
},
|
||||||
|
|
||||||
* NullableTypeAnnotation(node) {
|
* NullableTypeAnnotation(node: t.NullableTypeAnnotation): E {
|
||||||
return ['null', yield node.typeAnnotation];
|
return ['null', yield node.typeAnnotation];
|
||||||
},
|
},
|
||||||
|
|
||||||
* NullLiteralTypeAnnotation(node) {
|
* NullLiteralTypeAnnotation(node: t.NullLiteralTypeAnnotation): E {
|
||||||
return 'null';
|
return 'null';
|
||||||
},
|
},
|
||||||
|
|
||||||
* StringLiteralTypeAnnotation(node) {
|
* StringLiteralTypeAnnotation(node: t.StringLiteralTypeAnnotation): E {
|
||||||
return {
|
return {
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
symbols: [node.value],
|
symbols: [node.value],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
* GenericTypeAnnotation(node) {
|
* GenericTypeAnnotation(node: t.GenericTypeAnnotation): E {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
const params = node.typeParameters && (yield node.typeParameters);
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
|
@ -218,27 +222,27 @@ export const definition = {
|
||||||
return makeFullname(schema);
|
return makeFullname(schema);
|
||||||
},
|
},
|
||||||
|
|
||||||
* TypeParameterInstantiation(node) {
|
* TypeParameterInstantiation(node: t.TypeParameterInstantiation): E {
|
||||||
return yield node.params;
|
return yield node.params;
|
||||||
},
|
},
|
||||||
|
|
||||||
* FunctionTypeAnnotation(node) {
|
* FunctionTypeAnnotation(node: t.FunctionTypeAnnotation): E {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
* InterfaceExtends(node) {
|
* InterfaceExtends(node: t.InterfaceExtends): E {
|
||||||
return yield node.id;
|
return yield node.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
* Identifier(node) {
|
* Identifier(node: t.Identifier): E {
|
||||||
return node.name;
|
return node.name;
|
||||||
},
|
},
|
||||||
|
|
||||||
* CommentLine(node) {
|
* CommentLine(node: t.CommentLine): E {
|
||||||
return extractPragma(node.value);
|
return extractPragma(node.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
* CommentBlock(node) {
|
* CommentBlock(node: t.CommentBlock): E {
|
||||||
return extractPragma(node.value);
|
return extractPragma(node.value);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -260,11 +264,11 @@ export const declaration = {
|
||||||
* Blocks.
|
* Blocks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* Program(node) {
|
* Program(node: t.Program): E {
|
||||||
yield node.body;
|
yield node.body;
|
||||||
},
|
},
|
||||||
|
|
||||||
* BlockStatement(node) {
|
* BlockStatement(node: t.BlockStatement): E {
|
||||||
yield enter();
|
yield enter();
|
||||||
yield node.body;
|
yield node.body;
|
||||||
yield exit();
|
yield exit();
|
||||||
|
@ -277,7 +281,7 @@ export const declaration = {
|
||||||
* TODO: support form "import *".
|
* TODO: support form "import *".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* ImportDeclaration(node) {
|
* ImportDeclaration(node: t.ImportDeclaration): E {
|
||||||
const specifiers = yield node.specifiers;
|
const specifiers = yield node.specifiers;
|
||||||
const path = yield node.source;
|
const path = yield node.source;
|
||||||
|
|
||||||
|
@ -288,21 +292,21 @@ export const declaration = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
* ImportDefaultSpecifier(node) {
|
* ImportDefaultSpecifier(node: t.ImportDefaultSpecifier): E {
|
||||||
return {
|
return {
|
||||||
local: yield node.local,
|
local: yield node.local,
|
||||||
imported: null,
|
imported: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
* ImportSpecifier(node) {
|
* ImportSpecifier(node: t.ImportSpecifier): E {
|
||||||
return {
|
return {
|
||||||
local: yield node.local,
|
local: yield node.local,
|
||||||
imported: yield node.imported,
|
imported: yield node.imported,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
* VariableDeclarator(node) {
|
* VariableDeclarator(node: t.VariableDeclarator): E {
|
||||||
const path = extractRequire(node.init);
|
const path = extractRequire(node.init);
|
||||||
|
|
||||||
if (!path) {
|
if (!path) {
|
||||||
|
@ -325,11 +329,11 @@ export const declaration = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
* ObjectPattern(node) {
|
* ObjectPattern(node: t.ObjectPattern): E {
|
||||||
return yield node.properties;
|
return yield node.properties;
|
||||||
},
|
},
|
||||||
|
|
||||||
* ObjectProperty(node) {
|
* ObjectProperty(node: t.ObjectProperty): E {
|
||||||
const key = yield node.key;
|
const key = yield node.key;
|
||||||
|
|
||||||
// TODO: different roots.
|
// TODO: different roots.
|
||||||
|
@ -337,7 +341,7 @@ export const declaration = {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//assert.equal(node.value.type, 'Identifier');
|
//invariant(node.value.type === 'Identifier');
|
||||||
|
|
||||||
const value = yield node.value;
|
const value = yield node.value;
|
||||||
|
|
||||||
|
@ -354,7 +358,7 @@ export const declaration = {
|
||||||
* TODO: support commonjs.
|
* TODO: support commonjs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* ExportDefaultDeclaration(node) {
|
* ExportDefaultDeclaration(node: t.ExportDefaultDeclaration): E {
|
||||||
const reference = yield node.declaration;
|
const reference = yield node.declaration;
|
||||||
|
|
||||||
if (reference) {
|
if (reference) {
|
||||||
|
@ -362,7 +366,7 @@ export const declaration = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
* ExportNamedDeclaration(node) {
|
* ExportNamedDeclaration(node: t.ExportNamedDeclaration): E {
|
||||||
if (!node.declaration) {
|
if (!node.declaration) {
|
||||||
yield node.specifiers;
|
yield node.specifiers;
|
||||||
return;
|
return;
|
||||||
|
@ -375,7 +379,7 @@ export const declaration = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
* ExportSpecifier(node) {
|
* ExportSpecifier(node: t.ExportSpecifier): E {
|
||||||
const reference = yield node.local;
|
const reference = yield node.local;
|
||||||
let name = yield node.exported;
|
let name = yield node.exported;
|
||||||
|
|
||||||
|
@ -390,7 +394,7 @@ export const declaration = {
|
||||||
* Declarations.
|
* Declarations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* TypeAlias(node) {
|
* TypeAlias(node: t.TypeAlias): E {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
const params = node.typeParameters && (yield node.typeParameters);
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
|
@ -399,7 +403,7 @@ export const declaration = {
|
||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
* InterfaceDeclaration(node) {
|
* InterfaceDeclaration(node: t.InterfaceDeclaration): E {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
const params = node.typeParameters && (yield node.typeParameters);
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
|
@ -408,24 +412,24 @@ export const declaration = {
|
||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
* ClassDeclaration(node) {
|
* ClassDeclaration(node: t.ClassDeclaration): E {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
const params = node.typeParameters && (yield node.typeParameters);
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
// TODO: do it only for "all"-mode.
|
// TODO: do it only for "all"-mode.
|
||||||
const body = node.body;
|
const body = node.body;
|
||||||
yield body.body.filter(is('ClassMethod'));
|
yield body.body.filter(n => t.isClassMethod(n));
|
||||||
|
|
||||||
yield declare(name, node, params);
|
yield declare(name, node, params);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
* TypeParameterDeclaration(node) {
|
* TypeParameterDeclaration(node: t.TypeParameterDeclaration): E {
|
||||||
return yield node.params;
|
return yield node.params;
|
||||||
},
|
},
|
||||||
|
|
||||||
* TypeParameter(node) {
|
* TypeParameter(node: t.TypeParameter): E {
|
||||||
return {
|
return {
|
||||||
name: node.name,
|
name: node.name,
|
||||||
default: node.default ? yield node.default : null,
|
default: node.default ? yield node.default : null,
|
||||||
|
@ -436,16 +440,16 @@ export const declaration = {
|
||||||
* Utility.
|
* Utility.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* StringLiteral(node) {
|
* StringLiteral(node: t.StringLiteral): E {
|
||||||
return node.value;
|
return node.value;
|
||||||
},
|
},
|
||||||
|
|
||||||
* Identifier(node) {
|
* Identifier(node: t.Identifier): E {
|
||||||
return node.name;
|
return node.name;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function* extractLastPragma(comments) {
|
function* extractLastPragma(comments: t.Comment[]): ?string {
|
||||||
const pragmas = (yield comments).filter(Boolean);
|
const pragmas = (yield comments).filter(Boolean);
|
||||||
|
|
||||||
return pragmas.length > 0 ? pragmas[pragmas.length - 1] : null;
|
return pragmas.length > 0 ? pragmas[pragmas.length - 1] : null;
|
||||||
|
@ -481,6 +485,7 @@ function* extractProperty(prop, value) {
|
||||||
function extractRequire(node) {
|
function extractRequire(node) {
|
||||||
// XXX: refactor it!
|
// XXX: refactor it!
|
||||||
|
|
||||||
|
// TODO: use `t.*` helpers.
|
||||||
const ok = node &&
|
const ok = node &&
|
||||||
node.type === 'CallExpression' &&
|
node.type === 'CallExpression' &&
|
||||||
node.callee.type === 'Identifier' &&
|
node.callee.type === 'Identifier' &&
|
||||||
|
@ -493,7 +498,7 @@ function extractRequire(node) {
|
||||||
const argument = node.arguments[0];
|
const argument = node.arguments[0];
|
||||||
|
|
||||||
// TODO: warning about dynamic imports.
|
// TODO: warning about dynamic imports.
|
||||||
assert.equal(argument.type, 'StringLiteral');
|
invariant(t.isStringLiteral(argument));
|
||||||
|
|
||||||
return argument.value;
|
return argument.value;
|
||||||
}
|
}
|
||||||
|
@ -531,7 +536,7 @@ function extractPragma(text) {
|
||||||
|
|
||||||
const pair = parsePragma(pragma);
|
const pair = parsePragma(pragma);
|
||||||
|
|
||||||
assert.ok(pair);
|
invariant(pair);
|
||||||
|
|
||||||
const [type, arg] = pair;
|
const [type, arg] = pair;
|
||||||
|
|
||||||
|
@ -570,12 +575,12 @@ function unwrapEnumSymbol(node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeFullname(schema) {
|
function makeFullname(schema) {
|
||||||
assert.ok(schema.namespace);
|
invariant(schema.namespace);
|
||||||
|
|
||||||
return `${schema.namespace}.${schema.name}`;
|
return `${schema.namespace}.${schema.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeSchemas(schemas) {
|
function mergeSchemas(schemas: Schemas): Schema {
|
||||||
const map = new Map;
|
const map = new Map;
|
||||||
|
|
||||||
// TODO: overriding?
|
// TODO: overriding?
|
||||||
|
@ -584,7 +589,7 @@ function mergeSchemas(schemas) {
|
||||||
|
|
||||||
for (const schema of schemas) {
|
for (const schema of schemas) {
|
||||||
// TODO: enums?
|
// TODO: enums?
|
||||||
assert.equal(schema.type, 'record');
|
invariant(schema.type === 'record');
|
||||||
|
|
||||||
for (const field of schema.fields) {
|
for (const field of schema.fields) {
|
||||||
const stored = map.get(field.name);
|
const stored = map.get(field.name);
|
||||||
|
@ -592,7 +597,7 @@ function mergeSchemas(schemas) {
|
||||||
if (stored) {
|
if (stored) {
|
||||||
// TODO: what about enums?
|
// TODO: what about enums?
|
||||||
// TODO: improve checking.
|
// TODO: improve checking.
|
||||||
assert.equal(stored.type, field.type);
|
invariant(stored.type === field.type);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,7 +613,3 @@ function mergeSchemas(schemas) {
|
||||||
fields: Array.from(map.values()),
|
fields: Array.from(map.values()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function is(type) {
|
|
||||||
return node => Boolean(node) && node.type === type;
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Collector from './collector';
|
||||||
// @see babel#6805.
|
// @see babel#6805.
|
||||||
//export {Parser, Collector};
|
//export {Parser, Collector};
|
||||||
|
|
||||||
export default function collect(path) {
|
export default function collect(path: string): Collector {
|
||||||
const parser = new Parser;
|
const parser = new Parser;
|
||||||
const collector = new Collector(parser);
|
const collector = new Collector(parser);
|
||||||
|
|
||||||
|
|
51
src/list.js
51
src/list.js
|
@ -1,46 +1,51 @@
|
||||||
import * as assert from 'assert';
|
import {invariant} from './utils';
|
||||||
|
|
||||||
|
export default class CircularList<T: Object> {
|
||||||
|
_mark: Symbol;
|
||||||
|
_prev: ?T;
|
||||||
|
_walk: ?T;
|
||||||
|
|
||||||
export default class CircularList {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mark = Symbol();
|
this._mark = Symbol();
|
||||||
this.prev = null;
|
this._prev = null;
|
||||||
this.walk = null;
|
this._walk = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEmpty() {
|
get isEmpty(): boolean {
|
||||||
return !this.walk;
|
return !this._walk;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(entry) {
|
add(entry: T) {
|
||||||
assert.ok(!entry[this.mark]);
|
invariant(!entry[this._mark]);
|
||||||
|
|
||||||
if (this.prev) {
|
if (this._prev) {
|
||||||
assert.ok(this.walk);
|
invariant(this._walk);
|
||||||
|
|
||||||
this.prev = this.prev[this.mark] = entry;
|
this._prev = this._prev[this._mark] = entry;
|
||||||
} else {
|
} else {
|
||||||
assert.ok(!this.walk);
|
invariant(!this._walk);
|
||||||
|
|
||||||
this.walk = this.prev = entry;
|
this._walk = this._prev = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry[this.mark] = this.walk;
|
entry[this._mark] = this._walk;
|
||||||
|
|
||||||
assert.ok(!this.prev || this.prev[this.mark] === this.walk);
|
invariant(!this._prev || this._prev[this._mark] === this._walk);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove() {
|
remove(): T {
|
||||||
assert.ok(this.walk);
|
invariant(this._walk);
|
||||||
|
|
||||||
const removed = this.walk;
|
const removed = this._walk;
|
||||||
|
|
||||||
if (removed === this.prev) {
|
if (removed === this._prev) {
|
||||||
this.walk = this.prev = null;
|
this._walk = this._prev = null;
|
||||||
} else {
|
} else {
|
||||||
this.walk = this.prev[this.mark] = removed[this.mark];
|
invariant(this._prev);
|
||||||
|
this._walk = this._prev[this._mark] = removed[this._mark];
|
||||||
}
|
}
|
||||||
|
|
||||||
removed[this.mark] = null;
|
removed[this._mark] = null;
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,32 @@
|
||||||
import * as pathlib from 'path';
|
import * as pathlib from 'path';
|
||||||
import * as resolve from 'resolve';
|
import * as resolve from 'resolve';
|
||||||
|
|
||||||
|
import type Scope from './scope';
|
||||||
|
import type {Schema} from './schema';
|
||||||
|
import type {Query} from './query';
|
||||||
|
|
||||||
export default class Module {
|
export default class Module {
|
||||||
constructor(path, namespace) {
|
+path: string;
|
||||||
|
+namespace: string;
|
||||||
|
_scopeCount: number;
|
||||||
|
_exports: Map<?string, [Scope, string]>;
|
||||||
|
|
||||||
|
constructor(path: string, namespace: string) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
this.scopeCount = 0;
|
this._scopeCount = 0;
|
||||||
this._exports = new Map;
|
this._exports = new Map;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateScopeId() {
|
generateScopeId(): number {
|
||||||
return this.scopeCount++;
|
return this._scopeCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
addExport(name, scope, reference) {
|
addExport(name: ?string, scope: Scope, reference: string) {
|
||||||
this._exports.set(name, [scope, reference]);
|
this._exports.set(name, [scope, reference]);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(name, params) {
|
query(name: ?string, params: Schema[]): Query {
|
||||||
const result = this._exports.get(name);
|
const result = this._exports.get(name);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
@ -31,14 +40,14 @@ export default class Module {
|
||||||
return scope.query(reference, params);
|
return scope.query(reference, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(path) {
|
resolve(path: string): string {
|
||||||
const basedir = pathlib.dirname(this.path);
|
const basedir = pathlib.dirname(this.path);
|
||||||
|
|
||||||
// TODO: follow symlinks.
|
// TODO: follow symlinks.
|
||||||
return resolve.sync(path, {basedir});
|
return resolve.sync(path, {basedir});
|
||||||
}
|
}
|
||||||
|
|
||||||
exports() {
|
exports(): Iterator<[Scope, string]> {
|
||||||
return this._exports.values();
|
return this._exports.values();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import * as babylon from 'babylon';
|
import * as babylon from 'babylon';
|
||||||
|
import type {File} from '@babel/types';
|
||||||
|
|
||||||
export default class Parser {
|
export default class Parser {
|
||||||
parse(code) {
|
parse(code: string): File {
|
||||||
// This parse configuration is intended to be as permissive as possible.
|
// This parse configuration is intended to be as permissive as possible.
|
||||||
return babylon.parse(code, {
|
return babylon.parse(code, {
|
||||||
allowImportExportEverywhere: true,
|
allowImportExportEverywhere: true,
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import type {Node} from '@babel/types';
|
||||||
|
|
||||||
|
import type Scope from './scope';
|
||||||
|
import type {Schema} from './schema';
|
||||||
|
|
||||||
|
export type Query =
|
||||||
|
| Unknown
|
||||||
|
| Declaration
|
||||||
|
| Template
|
||||||
|
| Definition
|
||||||
|
| External;
|
||||||
|
|
||||||
|
export type Unknown = {
|
||||||
|
type: 'unknown',
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Declaration = {
|
||||||
|
type: 'declaration',
|
||||||
|
name: string,
|
||||||
|
node: Node,
|
||||||
|
scope: Scope,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Template = {
|
||||||
|
type: 'template',
|
||||||
|
name: string,
|
||||||
|
params: TemplateParam[],
|
||||||
|
instances: Instance[],
|
||||||
|
node: Node,
|
||||||
|
scope: Scope,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Definition = {
|
||||||
|
type: 'definition',
|
||||||
|
schema: Schema,
|
||||||
|
scope: Scope,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type External = {
|
||||||
|
type: 'external',
|
||||||
|
info: ExternalInfo,
|
||||||
|
scope: Scope,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateParam = {
|
||||||
|
name: string,
|
||||||
|
default: Schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Instance = {
|
||||||
|
params: Schema[],
|
||||||
|
schema: Schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExternalInfo = {
|
||||||
|
local: string,
|
||||||
|
imported: ?string,
|
||||||
|
path: string,
|
||||||
|
};
|
|
@ -0,0 +1,77 @@
|
||||||
|
// @see flow#3912.
|
||||||
|
export type Schema =
|
||||||
|
| RecordType & Top
|
||||||
|
| EnumType & Top
|
||||||
|
| ArrayType & Top
|
||||||
|
| MapType & Top
|
||||||
|
| UnionType & Top
|
||||||
|
| FixedType & Top
|
||||||
|
| WrappedType & Top;
|
||||||
|
|
||||||
|
export type Top = {
|
||||||
|
name: string,
|
||||||
|
namespace?: string,
|
||||||
|
$unwrap?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Type =
|
||||||
|
| ComplexType
|
||||||
|
| PrimitiveType
|
||||||
|
| ReferenceType;
|
||||||
|
|
||||||
|
export type WrappedType = {type: Type};
|
||||||
|
|
||||||
|
export type ComplexType =
|
||||||
|
| RecordType
|
||||||
|
| EnumType
|
||||||
|
| ArrayType
|
||||||
|
| MapType
|
||||||
|
| UnionType
|
||||||
|
| FixedType
|
||||||
|
| WrappedType;
|
||||||
|
|
||||||
|
export type PrimitiveType =
|
||||||
|
| 'null'
|
||||||
|
| 'boolean'
|
||||||
|
| 'int'
|
||||||
|
| 'long'
|
||||||
|
| 'float'
|
||||||
|
| 'double'
|
||||||
|
| 'bytes'
|
||||||
|
| 'string';
|
||||||
|
|
||||||
|
export type ReferenceType = string;
|
||||||
|
|
||||||
|
export type RecordType = {
|
||||||
|
type: 'record',
|
||||||
|
name: string,
|
||||||
|
fields: FieldType[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FieldType = {
|
||||||
|
name: string,
|
||||||
|
type: Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EnumType = {
|
||||||
|
type: 'enum',
|
||||||
|
name: string,
|
||||||
|
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,
|
||||||
|
};
|
94
src/scope.js
94
src/scope.js
|
@ -1,7 +1,17 @@
|
||||||
import * as assert from 'assert';
|
import type {Node} from '@babel/types';
|
||||||
|
|
||||||
|
import {invariant} from './utils';
|
||||||
|
import type Module from './module';
|
||||||
|
import type {Schema} from './schema';
|
||||||
|
import type {Query, Template, TemplateParam, ExternalInfo} from './query';
|
||||||
|
|
||||||
export default class Scope {
|
export default class Scope {
|
||||||
static global(schemas) {
|
+parent: ?Scope;
|
||||||
|
+module: ?Module;
|
||||||
|
+scopeId: ?number;
|
||||||
|
_entries: Map<string, Query>;
|
||||||
|
|
||||||
|
static global(schemas: Schema[]) {
|
||||||
const global = new Scope(null, null);
|
const global = new Scope(null, null);
|
||||||
|
|
||||||
for (const schema of schemas) {
|
for (const schema of schemas) {
|
||||||
|
@ -11,104 +21,102 @@ export default class Scope {
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(parent, module) {
|
constructor(parent: ?Scope, module: ?Module) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.module = module;
|
this.module = module;
|
||||||
this.scopeId = module && module.generateScopeId();
|
this.scopeId = module && module.generateScopeId();
|
||||||
this.entries = new Map;
|
this._entries = new Map;
|
||||||
}
|
}
|
||||||
|
|
||||||
get namespace() {
|
get namespace(): string {
|
||||||
assert.ok(this.module);
|
invariant(this.module);
|
||||||
|
|
||||||
let namespace = this.module.namespace;
|
let namespace = this.module.namespace;
|
||||||
|
|
||||||
// Nested scopes.
|
// Nested scopes.
|
||||||
if (this.scopeId > 0) {
|
if (this.scopeId != null && this.scopeId > 0) {
|
||||||
namespace += '._' + this.scopeId;
|
namespace += '._' + this.scopeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return namespace;
|
return namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(module = null) {
|
extend(module: ?Module = null): Scope {
|
||||||
return new Scope(this, module || this.module);
|
return new Scope(this, module || this.module);
|
||||||
}
|
}
|
||||||
|
|
||||||
addDeclaration(name, node, params) {
|
addDeclaration(name: string, node: Node, params: TemplateParam[]) {
|
||||||
assert.ok(!this.entries.has(name));
|
invariant(!this._entries.has(name));
|
||||||
|
|
||||||
const isTemplate = Boolean(params);
|
const entry = params.length > 0 ? {
|
||||||
|
type: 'template',
|
||||||
const entry = {
|
name,
|
||||||
type: isTemplate ? 'template' : 'declaration',
|
params,
|
||||||
|
instances: [],
|
||||||
|
node,
|
||||||
|
scope: this,
|
||||||
|
} : {
|
||||||
|
type: 'declaration',
|
||||||
name,
|
name,
|
||||||
node,
|
node,
|
||||||
scope: this,
|
scope: this,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isTemplate) {
|
this._entries.set(name, entry);
|
||||||
entry.params = params;
|
|
||||||
entry.instances = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entries.set(name, entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addInstance(name, schema, params) {
|
addInstance(name: string, schema: Schema, params: Schema[]) {
|
||||||
const template = this.entries.get(name);
|
const template = this._entries.get(name);
|
||||||
|
|
||||||
assert.ok(template);
|
invariant(template);
|
||||||
assert.equal(template.type, 'template');
|
invariant(template.type === 'template');
|
||||||
|
|
||||||
template.instances.push({params, schema});
|
template.instances.push({params, schema});
|
||||||
}
|
}
|
||||||
|
|
||||||
addDefinition(schema, declared) {
|
addDefinition(schema: Schema, declared: boolean) {
|
||||||
const decl = this.entries.get(schema.name);
|
const decl = this._entries.get(schema.name);
|
||||||
|
|
||||||
if (declared) {
|
if (declared) {
|
||||||
assert.ok(decl);
|
invariant(decl);
|
||||||
assert.equal(decl.type, 'declaration');
|
invariant(decl.type === 'declaration');
|
||||||
} else {
|
} else {
|
||||||
assert.ok(!decl);
|
invariant(!decl);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.entries.set(schema.name, {
|
this._entries.set(schema.name, {
|
||||||
type: 'definition',
|
type: 'definition',
|
||||||
schema,
|
schema,
|
||||||
scope: this,
|
scope: this,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addImport(info) {
|
addImport(info: ExternalInfo) {
|
||||||
assert.ok(!this.entries.has(info.local));
|
invariant(!this._entries.has(info.local));
|
||||||
|
|
||||||
this.entries.set(info.local, {
|
this._entries.set(info.local, {
|
||||||
type: 'external',
|
type: 'external',
|
||||||
info,
|
info,
|
||||||
scope: this,
|
scope: this,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addExport(name, reference) {
|
addExport(name: string, reference: string) {
|
||||||
assert.ok(this.module);
|
invariant(this.module);
|
||||||
|
|
||||||
this.module.addExport(name, this, reference);
|
this.module.addExport(name, this, reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(path) {
|
resolve(path: string): string {
|
||||||
assert.ok(this.module);
|
invariant(this.module);
|
||||||
|
|
||||||
return this.module.resolve(path);
|
return this.module.resolve(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(name, params) {
|
query(name: string, params: Schema[]): Query {
|
||||||
const entry = this.entries.get(name);
|
const entry = this._entries.get(name);
|
||||||
|
|
||||||
if (entry && entry.type === 'template') {
|
if (entry && entry.type === 'template') {
|
||||||
assert.ok(params);
|
|
||||||
|
|
||||||
const augmented = entry.params.map((p, i) => params[i] || p.default);
|
const augmented = entry.params.map((p, i) => params[i] || p.default);
|
||||||
const schema = findInstance(entry, augmented);
|
const schema = findInstance(entry, augmented);
|
||||||
|
|
||||||
|
@ -126,7 +134,7 @@ export default class Scope {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
return this.parent.query(name);
|
return this.parent.query(name, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -135,7 +143,7 @@ export default class Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findInstance(template, queried) {
|
function findInstance(template: Template, queried: Schema[]): ?Schema {
|
||||||
for (const {schema, params} of template.instances) {
|
for (const {schema, params} of template.instances) {
|
||||||
// TODO: compare complex structures.
|
// TODO: compare complex structures.
|
||||||
const same = params.every((p, i) => p === queried[i]);
|
const same = params.every((p, i) => p === queried[i]);
|
||||||
|
|
11
src/utils.js
11
src/utils.js
|
@ -1,4 +1,6 @@
|
||||||
export function partition(iter, predicate) {
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
export function partition<T>(iter: Iterable<T>, predicate: T => boolean): [T[], T[]] {
|
||||||
const left = [];
|
const left = [];
|
||||||
const right = [];
|
const right = [];
|
||||||
|
|
||||||
|
@ -9,6 +11,11 @@ export function partition(iter, predicate) {
|
||||||
return [left, right];
|
return [left, right];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNode(it) {
|
// TODO: avoid it?
|
||||||
|
export function isNode(it: any): boolean %checks {
|
||||||
return it && typeof it === 'object' && it.type;
|
return it && typeof it === 'object' && it.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I so much dream about the user guards...
|
||||||
|
// @see flow#112.
|
||||||
|
export const invariant = assert.ok;
|
||||||
|
|
Loading…
Reference in New Issue