Project restructuring

master
Paul Loyd 2017-11-02 17:59:00 +03:00
parent fa0f96ebea
commit 9706b554df
8 changed files with 145 additions and 122 deletions

View File

@ -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,
});

100
lib/collector.js Normal file
View File

@ -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;

15
lib/commands.js Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);