Project restructuring
parent
fa0f96ebea
commit
9706b554df
|
@ -8,7 +8,7 @@ const argv = require('optimist')
|
|||
.usage('Usage: $0 <path> ...')
|
||||
.argv;
|
||||
|
||||
const generate = require('..');
|
||||
const collect = require('..');
|
||||
|
||||
argv._.forEach(run);
|
||||
|
||||
|
@ -18,10 +18,9 @@ function run(path) {
|
|||
}
|
||||
|
||||
try {
|
||||
const code = fs.readFileSync(path, 'utf8');
|
||||
const schemes = generate(code);
|
||||
const {schemas} = collect(path);
|
||||
|
||||
console.dir(schemes, {
|
||||
console.dir(schemas, {
|
||||
colors: true,
|
||||
depth: Infinity,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const extractors = require('./extractors');
|
||||
const Command = require('./commands');
|
||||
const {log} = require('./utils');
|
||||
|
||||
class Collector {
|
||||
constructor(parser) {
|
||||
this.parser = parser;
|
||||
this.schemas = Object.create(null); // TODO: use `Map`.
|
||||
}
|
||||
|
||||
collect(path) {
|
||||
const code = fs.readFileSync(path, 'utf8');
|
||||
const ast = this.parser.parse(code);
|
||||
|
||||
this._visit(ast);
|
||||
}
|
||||
|
||||
// Given the AST output of babylon parse, walk through in a depth-first order.
|
||||
_visit(ast) {
|
||||
let stack;
|
||||
let parent;
|
||||
let keys = [];
|
||||
let index = -1;
|
||||
|
||||
do {
|
||||
++index;
|
||||
|
||||
if (stack && index === keys.length) {
|
||||
parent = stack.parent;
|
||||
keys = stack.keys;
|
||||
index = stack.index;
|
||||
stack = stack.prev;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = parent ? parent[keys[index]] : ast.program;
|
||||
|
||||
if (isNode(node)) {
|
||||
const {type} = node;
|
||||
|
||||
if (type) {
|
||||
if (type in extractors) {
|
||||
this._collect(node);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
stack = { parent, keys, index, prev: stack };
|
||||
parent = node;
|
||||
keys = Object.keys(node);
|
||||
index = -1;
|
||||
}
|
||||
} while (stack);
|
||||
}
|
||||
|
||||
_collect(node) {
|
||||
const extractor = extractors[node.type];
|
||||
|
||||
if (!extractor) {
|
||||
log(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
const iter = extractor(node);
|
||||
|
||||
let result = null;
|
||||
|
||||
while (true) {
|
||||
const {done, value} = iter.next(result);
|
||||
|
||||
if (done) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
result = null;
|
||||
} else if (value instanceof Command && value.name === 'provide') {
|
||||
const {schema} = value;
|
||||
|
||||
this.schemas[schema.name] = schema;
|
||||
} else if (Array.isArray(value)) {
|
||||
result = value.map(val => this._collect(val));
|
||||
} else {
|
||||
result = this._collect(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNode(it) {
|
||||
return it && typeof it === 'object' && (it.type || it.length);
|
||||
}
|
||||
|
||||
module.exports = Collector;
|
|
@ -0,0 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
class Command {
|
||||
static provide(schema) {
|
||||
return new Command('provide', {schema});
|
||||
}
|
||||
|
||||
constructor(name, fields) {
|
||||
this.name = name;
|
||||
|
||||
Object.assign(this, fields);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
|
@ -2,43 +2,9 @@
|
|||
|
||||
const assert = require('assert');
|
||||
|
||||
const {provide} = require('./commands');
|
||||
const {partition, log} = require('./utils');
|
||||
|
||||
const provide = (schema) => ({command: 'provide', schema});
|
||||
|
||||
function extract(node, schemas) {
|
||||
const extractor = extractors[node.type];
|
||||
|
||||
if (!extractor) {
|
||||
log(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
const iter = extractor(node);
|
||||
|
||||
let result = null;
|
||||
|
||||
while (true) {
|
||||
const {done, value} = iter.next(result);
|
||||
|
||||
if (done) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
result = null;
|
||||
} else if (value.command === 'provide') {
|
||||
const {schema} = value;
|
||||
|
||||
schemas[schema.name] = schema;
|
||||
} else if (Array.isArray(value)) {
|
||||
result = value.map(val => extract(val, schemas));
|
||||
} else {
|
||||
result = extract(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const extractors = {
|
||||
* TypeAlias(node) {
|
||||
const schema = yield node.right;
|
||||
|
@ -297,4 +263,4 @@ function unwrapEnumSymbol(node) {
|
|||
return node.value;
|
||||
}
|
||||
|
||||
module.exports = extract;
|
||||
module.exports = extractors;
|
34
lib/index.js
34
lib/index.js
|
@ -1,29 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
const parse = require('./parser');
|
||||
const visit = require('./visitor');
|
||||
const extract = require('./extractor');
|
||||
const Parser = require('./parser');
|
||||
const Collector = require('./collector');
|
||||
|
||||
function collect(node, schemas) {
|
||||
extract(node, schemas);
|
||||
function collect(path) {
|
||||
const parser = new Parser;
|
||||
const collector = new Collector(parser);
|
||||
|
||||
return false;
|
||||
collector.collect(path);
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
const visitor = {
|
||||
TypeAlias: collect,
|
||||
InterfaceDeclaration: collect,
|
||||
ClassDeclaration: collect,
|
||||
};
|
||||
|
||||
function generate(code) {
|
||||
const ast = parse(code);
|
||||
|
||||
const schemas = Object.create({});
|
||||
|
||||
visit(ast, visitor, schemas);
|
||||
|
||||
return schemas;
|
||||
}
|
||||
|
||||
module.exports = generate;
|
||||
module.exports = collect;
|
||||
module.exports.Parser = Parser;
|
||||
module.exports.Collector = Collector;
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
const babylon = require('babylon');
|
||||
|
||||
function parse(code) {
|
||||
// This parse configuration is intended to be as permissive as possible.
|
||||
return babylon.parse(code, {
|
||||
allowImportExportEverywhere: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
sourceType: 'module',
|
||||
plugins: [ '*', 'jsx', 'flow' ],
|
||||
});
|
||||
class Parser {
|
||||
parse(code) {
|
||||
// This parse configuration is intended to be as permissive as possible.
|
||||
return babylon.parse(code, {
|
||||
allowImportExportEverywhere: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
sourceType: 'module',
|
||||
plugins: [ '*', 'jsx', 'flow' ],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = parse;
|
||||
module.exports = Parser;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// Given the AST output of babylon parse, walk through in a depth-first order,
|
||||
// calling methods on the given visitor, providing context as the first argument.
|
||||
function visit(ast, visitor, state) {
|
||||
let stack;
|
||||
let parent;
|
||||
let keys = [];
|
||||
let index = -1;
|
||||
|
||||
do {
|
||||
++index;
|
||||
|
||||
if (stack && index === keys.length) {
|
||||
parent = stack.parent;
|
||||
keys = stack.keys;
|
||||
index = stack.index;
|
||||
stack = stack.prev;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = parent ? parent[keys[index]] : ast.program;
|
||||
|
||||
if (node && typeof node === 'object' && (node.type || node.length)) {
|
||||
const {type} = node;
|
||||
|
||||
if (type) {
|
||||
if (type in visitor) {
|
||||
const stop = visitor[type](node, state) === false;
|
||||
|
||||
if (stop) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stack = { parent, keys, index, prev: stack };
|
||||
parent = node;
|
||||
keys = Object.keys(node);
|
||||
index = -1;
|
||||
}
|
||||
} while (stack);
|
||||
}
|
||||
|
||||
module.exports = visit;
|
5
tests/do
5
tests/do
|
@ -6,7 +6,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const jsondiffpatch = require('jsondiffpatch');
|
||||
|
||||
const generate = require('..');
|
||||
const collect = require('..');
|
||||
|
||||
const runner = path.basename(__filename);
|
||||
|
||||
|
@ -16,8 +16,7 @@ const list = fs.readdirSync(__dirname)
|
|||
|
||||
for (const fpath of list) {
|
||||
const code = fs.readFileSync(fpath, 'utf8');
|
||||
|
||||
const actual = generate(code);
|
||||
const {schemas: actual} = collect(fpath);
|
||||
const expected = eval(code.split('// ###')[1]);
|
||||
|
||||
const delta = jsondiffpatch.diff(actual, expected);
|
||||
|
|
Loading…
Reference in New Issue