Add the scope concept
parent
9706b554df
commit
492a9e9af8
|
@ -1,21 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const extractors = require('./extractors');
|
||||
const Command = require('./commands');
|
||||
const Scope = require('./scope');
|
||||
const {log} = require('./utils');
|
||||
|
||||
class Collector {
|
||||
constructor(parser) {
|
||||
this.parser = parser;
|
||||
this.schemas = Object.create(null); // TODO: use `Map`.
|
||||
this.scope = null;
|
||||
}
|
||||
|
||||
collect(path) {
|
||||
const code = fs.readFileSync(path, 'utf8');
|
||||
const ast = this.parser.parse(code);
|
||||
|
||||
this.scope = new Scope(null);
|
||||
|
||||
this._visit(ast);
|
||||
}
|
||||
|
||||
|
@ -43,12 +48,10 @@ class Collector {
|
|||
if (isNode(node)) {
|
||||
const {type} = node;
|
||||
|
||||
if (type) {
|
||||
if (type in extractors) {
|
||||
this._collect(node);
|
||||
if (type && type in extractors) {
|
||||
this._collect(node);
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
stack = { parent, keys, index, prev: stack };
|
||||
|
@ -80,13 +83,32 @@ class Collector {
|
|||
|
||||
if (!value) {
|
||||
result = null;
|
||||
} else if (value instanceof Command && value.name === 'provide') {
|
||||
const {schema} = value;
|
||||
|
||||
this.schemas[schema.name] = schema;
|
||||
} else if (value instanceof Command) {
|
||||
switch (value.name) {
|
||||
case 'provide':
|
||||
assert(this.scope);
|
||||
this.scope.addSchema(value.data);
|
||||
this.schemas[value.data.name] = value.data;
|
||||
break;
|
||||
case 'query':
|
||||
const schema = this.scope.query(value.data);
|
||||
assert(schema, value.data);
|
||||
result = schema;
|
||||
break;
|
||||
case 'enter':
|
||||
this.scope = new Scope(this.scope);
|
||||
break;
|
||||
case 'exit':
|
||||
assert(this.scope);
|
||||
this.scope = this.scope.parent;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
} else if (Array.isArray(value)) {
|
||||
result = value.map(val => this._collect(val));
|
||||
} else {
|
||||
assert(isNode(value));
|
||||
result = this._collect(value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,24 @@
|
|||
|
||||
class Command {
|
||||
static provide(schema) {
|
||||
return new Command('provide', {schema});
|
||||
return new Command('provide', schema);
|
||||
}
|
||||
|
||||
constructor(name, fields) {
|
||||
this.name = name;
|
||||
static query(name) {
|
||||
return new Command('query', name);
|
||||
}
|
||||
|
||||
Object.assign(this, fields);
|
||||
static enter() {
|
||||
return new Command('enter');
|
||||
}
|
||||
|
||||
static exit() {
|
||||
return new Command('exit');
|
||||
}
|
||||
|
||||
constructor(name, data) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const assert = require('assert');
|
||||
|
||||
const {provide} = require('./commands');
|
||||
const {provide, query} = require('./commands');
|
||||
const {partition, log} = require('./utils');
|
||||
|
||||
const extractors = {
|
||||
|
@ -170,6 +170,8 @@ const extractors = {
|
|||
return 'bytes';
|
||||
}
|
||||
|
||||
yield query(name);
|
||||
|
||||
return name;
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
'use strict';
|
||||
|
||||
class Scope {
|
||||
constructor(parent) {
|
||||
this.schemas = new Map;
|
||||
this.externals = new Map;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
addSchema(schema) {
|
||||
this.schemas.set(schema.name, schema);
|
||||
}
|
||||
|
||||
addExternal(external) {
|
||||
this.externals.set(external.name, external);
|
||||
}
|
||||
|
||||
query(name) {
|
||||
const result = this._querySchema(name) || this._queryExternal(name);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (this.parent) {
|
||||
return this.parent.query(name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_querySchema(name) {
|
||||
const schema = this.schemas.get(name);
|
||||
|
||||
return schema ? {
|
||||
type: 'local',
|
||||
schema,
|
||||
} : null;
|
||||
}
|
||||
|
||||
_queryExternal(name) {
|
||||
const info = this.externals.get(name);
|
||||
|
||||
return info ? {
|
||||
type: 'external',
|
||||
info,
|
||||
} : null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Scope;
|
4
tests/do
4
tests/do
|
@ -15,14 +15,14 @@ const list = fs.readdirSync(__dirname)
|
|||
.map(fname => path.relative('.', path.join(__dirname, fname)));
|
||||
|
||||
for (const fpath of list) {
|
||||
console.log(`${fpath}...`);
|
||||
|
||||
const code = fs.readFileSync(fpath, 'utf8');
|
||||
const {schemas: actual} = collect(fpath);
|
||||
const expected = eval(code.split('// ###')[1]);
|
||||
|
||||
const delta = jsondiffpatch.diff(actual, expected);
|
||||
|
||||
console.log(`${fpath}...`);
|
||||
|
||||
if (delta) {
|
||||
jsondiffpatch.console.log(delta);
|
||||
}
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
type A = {};
|
||||
|
||||
type Type = {
|
||||
a: A,
|
||||
b: B[],
|
||||
c: C<number>,
|
||||
b: A[],
|
||||
c: A<number>,
|
||||
};
|
||||
|
||||
interface Interface {
|
||||
a: A;
|
||||
b: B[];
|
||||
c: C<number>;
|
||||
b: A[];
|
||||
c: A<number>;
|
||||
}
|
||||
|
||||
class Class {
|
||||
a: A;
|
||||
b: B[];
|
||||
c: C<number>;
|
||||
b: A[];
|
||||
c: A<number>;
|
||||
}
|
||||
|
||||
// ###
|
||||
({
|
||||
A: {
|
||||
type: 'record',
|
||||
name: 'A',
|
||||
fields: [],
|
||||
},
|
||||
Type: {
|
||||
type: 'record',
|
||||
name: 'Type',
|
||||
fields: [
|
||||
{name: 'a', type: 'A'},
|
||||
{name: 'b', type: {type: 'array', items: 'B'}},
|
||||
{name: 'c', type: 'C'},
|
||||
{name: 'b', type: {type: 'array', items: 'A'}},
|
||||
{name: 'c', type: 'A'},
|
||||
],
|
||||
},
|
||||
Interface: {
|
||||
|
@ -32,8 +39,8 @@ class Class {
|
|||
name: 'Interface',
|
||||
fields: [
|
||||
{name: 'a', type: 'A'},
|
||||
{name: 'b', type: {type: 'array', items: 'B'}},
|
||||
{name: 'c', type: 'C'},
|
||||
{name: 'b', type: {type: 'array', items: 'A'}},
|
||||
{name: 'c', type: 'A'},
|
||||
],
|
||||
},
|
||||
Class: {
|
||||
|
@ -41,8 +48,8 @@ class Class {
|
|||
name: 'Class',
|
||||
fields: [
|
||||
{name: 'a', type: 'A'},
|
||||
{name: 'b', type: {type: 'array', items: 'B'}},
|
||||
{name: 'c', type: 'C'},
|
||||
{name: 'b', type: {type: 'array', items: 'A'}},
|
||||
{name: 'c', type: 'A'},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue