flow: flat context, object spread/destructure, return, general gardening
This commit is contained in:
parent
f5c9e20b26
commit
6fdf4e2327
|
@ -6,11 +6,22 @@ export default class Sandbox {
|
|||
|
||||
constructor(ast, context) {
|
||||
this.ast = ast;
|
||||
this.reset().setContext(context);
|
||||
this.reset();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
context() {
|
||||
return this.rootScope()?.getAll() || {};
|
||||
get context() {
|
||||
return this.rootScope?.context || {};
|
||||
}
|
||||
|
||||
set context(context) {
|
||||
const {rootScope} = this;
|
||||
if (rootScope) {
|
||||
rootScope.context = context;
|
||||
}
|
||||
else {
|
||||
this.setNodeScope(this.ast, new Scope(context));
|
||||
}
|
||||
}
|
||||
|
||||
evaluate(node) {
|
||||
|
@ -48,43 +59,34 @@ export default class Sandbox {
|
|||
value: value.then((value) => scope.set(left.name, value)),
|
||||
};
|
||||
}
|
||||
scope.set(left.name, value);
|
||||
return {value};
|
||||
return {value: scope.set(left.name, value)};
|
||||
}
|
||||
/* eslint-disable no-param-reassign */
|
||||
const assign = (O, P, value, optional) => {
|
||||
// eslint-disable-next-line object-curly-newline
|
||||
if (optional) {
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
O && (O[P] = value);
|
||||
}
|
||||
else {
|
||||
O[P] = value;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
/* eslint-enable no-param-reassign */
|
||||
const makeAsync = (O, P, value, optional) => (
|
||||
Promise
|
||||
.all([O, P, value])
|
||||
.then(([O, P, value]) => assign(O, P, value, optional))
|
||||
);
|
||||
const {
|
||||
computed,
|
||||
object,
|
||||
optional,
|
||||
property,
|
||||
} = left;
|
||||
const assign = (O, P, value) => {
|
||||
if (optional && !O) {
|
||||
return undefined;
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign, no-return-assign
|
||||
return O[P] = value;
|
||||
};
|
||||
const makeAsync = (O, P, value) => (
|
||||
Promise.all([O, P, value]).then(([O, P, value]) => assign(O, P, value))
|
||||
);
|
||||
const O = this.evaluate(object);
|
||||
const P = computed ? this.evaluate(property) : {value: property.name};
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if (Vasync | O.async | P.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: makeAsync(O.value, P.value, value, optional),
|
||||
value: makeAsync(O.value, P.value, value),
|
||||
};
|
||||
}
|
||||
return {value: assign(O.value, P.value, value, optional)};
|
||||
return {value: assign(O.value, P.value, value)};
|
||||
}
|
||||
|
||||
evaluateAwaitExpression({argument}) {
|
||||
|
@ -237,6 +239,7 @@ export default class Sandbox {
|
|||
let isAsync = false;
|
||||
const entries = [];
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (types.isObjectProperty(properties[i])) {
|
||||
const {computed, key, value} = properties[i];
|
||||
const k = computed ? this.evaluate(key) : {value: key.name};
|
||||
const v = this.evaluate(value);
|
||||
|
@ -249,10 +252,50 @@ export default class Sandbox {
|
|||
entries.push([k.value, v.value]);
|
||||
}
|
||||
}
|
||||
if (types.isSpreadElement(properties[i])) {
|
||||
const {argument} = properties[i];
|
||||
const spreading = this.evaluate(argument);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
isAsync |= spreading.async;
|
||||
if (spreading.async) {
|
||||
entries.push(Promise.resolve(spreading.value).then((spreading) => {
|
||||
const entries = [];
|
||||
const keys = Object.keys(spreading);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const key = keys[i];
|
||||
entries.push([key, spreading[key]]);
|
||||
}
|
||||
return entries;
|
||||
}));
|
||||
}
|
||||
else {
|
||||
const keys = Object.keys(spreading.value);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const key = keys[i];
|
||||
entries.push([key, spreading.value[key]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
async: !!isAsync,
|
||||
value: isAsync
|
||||
? Promise.all(entries).then(Object.fromEntries)
|
||||
? Promise.all(entries)
|
||||
.then((entries) => {
|
||||
const reduced = [];
|
||||
for (let i = 0; i < entries.length; ++i) {
|
||||
const entry = entries[i];
|
||||
if (Array.isArray(entry[0])) {
|
||||
for (let j = 0; j < entry.length; j++) {
|
||||
reduced.push(entry[j]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
reduced.push(entry);
|
||||
}
|
||||
}
|
||||
return Object.fromEntries(reduced);
|
||||
})
|
||||
: Object.fromEntries(entries),
|
||||
};
|
||||
}
|
||||
|
@ -335,13 +378,12 @@ export default class Sandbox {
|
|||
this.parents = new WeakMap();
|
||||
this.scopes = new WeakMap();
|
||||
this.runner = (function* traverse() {
|
||||
yield* self.traverse(self.ast);
|
||||
return undefined;
|
||||
return yield* self.traverse(self.ast);
|
||||
}());
|
||||
return this;
|
||||
}
|
||||
|
||||
rootScope() {
|
||||
get rootScope() {
|
||||
return this.nodeScope(this.ast);
|
||||
}
|
||||
|
||||
|
@ -385,11 +427,6 @@ export default class Sandbox {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
setContext(context) {
|
||||
this.setNodeScope(this.ast, new Scope(context || this.context()));
|
||||
return this;
|
||||
}
|
||||
|
||||
setNextScope(node, scope = this.nodeScope(node)) {
|
||||
const nodes = this.nextNodes(node, types.VISITOR_KEYS[node.type]);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
|
@ -405,6 +442,44 @@ export default class Sandbox {
|
|||
this.scopes.set(node, scope);
|
||||
}
|
||||
|
||||
destructure(id, init, scope) {
|
||||
this.setNextScope(id, scope);
|
||||
const {properties} = id;
|
||||
const promises = [];
|
||||
for (let i = 0; i < properties.length; ++i) {
|
||||
const property = properties[i];
|
||||
this.setNextScope(property, scope);
|
||||
const k = property.computed
|
||||
? this.evaluate(property.key)
|
||||
: {value: property.key.name};
|
||||
if (k.async) {
|
||||
promises.push(Promise.resolve(k.value).then((k) => {
|
||||
if (types.isIdentifier(property.value)) {
|
||||
const {name} = property.value;
|
||||
scope.allocate(name, init[k]);
|
||||
return undefined;
|
||||
}
|
||||
return this.destructure(property.value, init[k], scope);
|
||||
}));
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
if (types.isIdentifier(property.value)) {
|
||||
const {name} = property.value;
|
||||
scope.allocate(name, init[k.value]);
|
||||
}
|
||||
else {
|
||||
const promiseOrVoid = this.destructure(property.value, init[k.value], scope);
|
||||
if (promiseOrVoid) {
|
||||
promises.push(promiseOrVoid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return promises.length > 0
|
||||
? Promise.all(promises)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
* traverse(node) {
|
||||
let keys = types.VISITOR_KEYS[node.type];
|
||||
if (!keys) {
|
||||
|
@ -420,33 +495,47 @@ export default class Sandbox {
|
|||
}
|
||||
this.setNextScope(node, scope);
|
||||
if (types.isVariableDeclarator(node)) {
|
||||
const {id, init} = node;
|
||||
const {async, value} = this.evaluate(init);
|
||||
if (async) {
|
||||
const {id} = node;
|
||||
const init = this.evaluate(node.init);
|
||||
if (types.isIdentifier(id)) {
|
||||
if (init.async) {
|
||||
yield {
|
||||
async: true,
|
||||
value: Promise.resolve(value).then((value) => {
|
||||
value: Promise.resolve(init.value).then((value) => {
|
||||
scope.allocate(id.name, value);
|
||||
}),
|
||||
};
|
||||
}
|
||||
else {
|
||||
scope.allocate(id.name, value);
|
||||
scope.allocate(id.name, init.value);
|
||||
}
|
||||
}
|
||||
else if (types.isObjectPattern(id)) {
|
||||
const promiseOrVoid = init.async
|
||||
? Promise.resolve(init.value).then((init) => this.destructure(id, init, scope))
|
||||
: this.destructure(id, init.value, scope);
|
||||
if (promiseOrVoid) {
|
||||
yield {
|
||||
async: true,
|
||||
value: promiseOrVoid,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Blocks...
|
||||
if (types.isIfStatement(node)) {
|
||||
const {async, value} = this.evaluate(node.test);
|
||||
const branch = (value) => {
|
||||
keys = [value ? 'consequent' : 'alternate'];
|
||||
};
|
||||
if (async) {
|
||||
yield {
|
||||
async: true,
|
||||
value: Promise.resolve(value).then((value) => {
|
||||
keys = [value ? 'consequent' : 'alternate'];
|
||||
}),
|
||||
value: Promise.resolve(value).then(branch),
|
||||
};
|
||||
}
|
||||
else {
|
||||
keys = [value ? 'consequent' : 'alternate'];
|
||||
branch(value);
|
||||
}
|
||||
}
|
||||
// Loops...
|
||||
|
@ -480,7 +569,11 @@ export default class Sandbox {
|
|||
// Recur...
|
||||
const nodes = this.nextNodes(node, keys);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
yield* this.traverse(nodes[i]);
|
||||
const r = yield* this.traverse(nodes[i]);
|
||||
if (r) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
return r;
|
||||
}
|
||||
}
|
||||
// Loops...
|
||||
if (types.isForStatement(node)) {
|
||||
|
@ -520,6 +613,10 @@ export default class Sandbox {
|
|||
scope = scope.pop();
|
||||
}
|
||||
// Evaluate...
|
||||
if (types.isReturnStatement(node)) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
return this.evaluate(node.argument);
|
||||
}
|
||||
if (types.isDirective(node)) {
|
||||
yield this.evaluate(node.value);
|
||||
}
|
||||
|
|
|
@ -5,27 +5,18 @@ export default class Scope {
|
|||
context = {};
|
||||
|
||||
constructor(context = {}) {
|
||||
this.context = Object
|
||||
.fromEntries(
|
||||
Object.entries(context)
|
||||
.map(([key, value]) => [key, {value}]),
|
||||
);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
allocate(key, value) {
|
||||
if (this.context[key]) {
|
||||
this.context[key].value = value;
|
||||
}
|
||||
else {
|
||||
this.context[key] = {value};
|
||||
}
|
||||
this.context[key] = value;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
let walk = this;
|
||||
while (walk) {
|
||||
if (walk.context[key]) {
|
||||
return walk.context[key].value;
|
||||
if (key in walk.context) {
|
||||
return walk.context[key];
|
||||
}
|
||||
walk = walk.parent;
|
||||
}
|
||||
|
@ -39,7 +30,7 @@ export default class Scope {
|
|||
while (walk) {
|
||||
const entries = Object.entries(walk.context);
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const [key, {value}] = entries[i];
|
||||
const [key, value] = entries[i];
|
||||
if (!keys.has(key)) {
|
||||
keys.add(key);
|
||||
all[key] = value;
|
||||
|
@ -65,8 +56,8 @@ export default class Scope {
|
|||
set(key, value) {
|
||||
let walk = this;
|
||||
while (walk) {
|
||||
if (walk.context[key]) {
|
||||
walk.context[key].value = value;
|
||||
if (key in walk.context) {
|
||||
walk.context[key] = value;
|
||||
return value;
|
||||
}
|
||||
walk = walk.parent;
|
||||
|
|
|
@ -3,7 +3,52 @@ import {expect} from 'chai';
|
|||
|
||||
import Sandbox from '../src/sandbox';
|
||||
|
||||
it('evaluates AssignmentExpression', () => {
|
||||
expect(new Sandbox(parse('let foo = 69; foo = 420')).next().value)
|
||||
it('evaluates async AssignmentExpression', async () => {
|
||||
const o = {allowAwaitOutsideFunction: true};
|
||||
const sandbox = new Sandbox(parse('let foo = await 69; foo + 351', o));
|
||||
const {async, value} = sandbox.next().value;
|
||||
expect(async)
|
||||
.to.be.true;
|
||||
await value;
|
||||
expect(sandbox.next().value)
|
||||
.to.deep.include({value: 420});
|
||||
});
|
||||
|
||||
it('evaluates AssignmentExpression', () => {
|
||||
expect(new Sandbox(parse('let foo = 69; foo + 351')).next().value)
|
||||
.to.deep.include({value: 420});
|
||||
});
|
||||
|
||||
it('destructures', () => {
|
||||
const sandbox = new Sandbox(parse('const {a, b} = {a: 1, b: 2}; [a, b];'));
|
||||
expect(sandbox.next().value)
|
||||
.to.deep.include({value: [1, 2]});
|
||||
});
|
||||
|
||||
it('destructures async key', async () => {
|
||||
const o = {allowAwaitOutsideFunction: true};
|
||||
const sandbox = new Sandbox(parse('const k = "a"; const {[await k]: a, b} = {a: 1, b: 2}; [a, b];', o));
|
||||
const {async, value} = sandbox.next().value;
|
||||
expect(async)
|
||||
.to.be.true;
|
||||
await value;
|
||||
expect(sandbox.next().value)
|
||||
.to.deep.include({value: [1, 2]});
|
||||
});
|
||||
|
||||
it('destructures async value', async () => {
|
||||
const o = {allowAwaitOutsideFunction: true};
|
||||
const sandbox = new Sandbox(parse('const {a, b} = await {a: 1, b: 2}; [a, b];', o));
|
||||
const {async, value} = sandbox.next().value;
|
||||
expect(async)
|
||||
.to.be.true;
|
||||
await value;
|
||||
expect(sandbox.next().value)
|
||||
.to.deep.include({value: [1, 2]});
|
||||
});
|
||||
|
||||
it('nested destructures', () => {
|
||||
const sandbox = new Sandbox(parse('const {a, b: {c}} = {a: 1, b: {c: 2}}; [a, c];'));
|
||||
expect(sandbox.next().value)
|
||||
.to.deep.include({value: [1, 2]});
|
||||
});
|
||||
|
|
|
@ -5,9 +5,9 @@ import Sandbox from '../src/sandbox';
|
|||
|
||||
it('can update context', () => {
|
||||
const sandbox = new Sandbox(parse('foo = 420'), {foo: 69});
|
||||
expect(sandbox.context())
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({foo: 69});
|
||||
sandbox.run();
|
||||
expect(sandbox.context())
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({foo: 420});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,38 @@ import {expect} from 'chai';
|
|||
|
||||
import Sandbox from '../src/sandbox';
|
||||
|
||||
it('evaluates async ObjectExpression', async () => {
|
||||
const context = {
|
||||
b: () => Promise.resolve(2),
|
||||
};
|
||||
const o = {allowAwaitOutsideFunction: true};
|
||||
const sandbox = new Sandbox(parse('({a: 1, b: await b()})', o), context);
|
||||
const {async, value} = sandbox.next().value;
|
||||
expect(async).to.be.true;
|
||||
expect(await value)
|
||||
.to.deep.include({a: 1, b: 2});
|
||||
});
|
||||
|
||||
it('evaluates ObjectExpression', () => {
|
||||
expect(new Sandbox(parse('({a: 1, b: 2})')).next().value)
|
||||
.to.deep.include({value: {a: 1, b: 2}});
|
||||
});
|
||||
|
||||
it('evaluates async SpreadElement', async () => {
|
||||
const context = {
|
||||
b: () => Promise.resolve({a: 2}),
|
||||
};
|
||||
const o = {allowAwaitOutsideFunction: true};
|
||||
const sandbox = new Sandbox(parse('({a: 1, ...(await b())})', o), context);
|
||||
const {async, value} = sandbox.next().value;
|
||||
expect(async).to.be.true;
|
||||
expect(await value)
|
||||
.to.deep.include({a: 2});
|
||||
});
|
||||
|
||||
it('evaluates SpreadElement', () => {
|
||||
expect(new Sandbox(parse('({...({a: 1}), a: 2})')).next().value)
|
||||
.to.deep.include({value: {a: 2}});
|
||||
expect(new Sandbox(parse('({a: 0, ...({a: 1})})')).next().value)
|
||||
.to.deep.include({value: {a: 1}});
|
||||
});
|
||||
|
|
14
packages/sandbox/test/return.js
Normal file
14
packages/sandbox/test/return.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {parse} from '@babel/parser';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import Sandbox from '../src/sandbox';
|
||||
|
||||
it('evaluates ReturnStatement', async () => {
|
||||
const o = {allowReturnOutsideFunction: true};
|
||||
const sandbox = new Sandbox(parse('return 69', o));
|
||||
const {done, value} = sandbox.next();
|
||||
expect(done)
|
||||
.to.be.true;
|
||||
expect(value.value)
|
||||
.to.deep.equal(69);
|
||||
});
|
Loading…
Reference in New Issue
Block a user