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) {
|
constructor(ast, context) {
|
||||||
this.ast = ast;
|
this.ast = ast;
|
||||||
this.reset().setContext(context);
|
this.reset();
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
context() {
|
get context() {
|
||||||
return this.rootScope()?.getAll() || {};
|
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) {
|
evaluate(node) {
|
||||||
|
@ -48,43 +59,34 @@ export default class Sandbox {
|
||||||
value: value.then((value) => scope.set(left.name, value)),
|
value: value.then((value) => scope.set(left.name, value)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
scope.set(left.name, value);
|
return {value: scope.set(left.name, value)};
|
||||||
return {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 {
|
const {
|
||||||
computed,
|
computed,
|
||||||
object,
|
object,
|
||||||
optional,
|
optional,
|
||||||
property,
|
property,
|
||||||
} = left;
|
} = 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 O = this.evaluate(object);
|
||||||
const P = computed ? this.evaluate(property) : {value: property.name};
|
const P = computed ? this.evaluate(property) : {value: property.name};
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
if (Vasync | O.async | P.async) {
|
if (Vasync | O.async | P.async) {
|
||||||
return {
|
return {
|
||||||
async: true,
|
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}) {
|
evaluateAwaitExpression({argument}) {
|
||||||
|
@ -237,6 +239,7 @@ export default class Sandbox {
|
||||||
let isAsync = false;
|
let isAsync = false;
|
||||||
const entries = [];
|
const entries = [];
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
|
if (types.isObjectProperty(properties[i])) {
|
||||||
const {computed, key, value} = properties[i];
|
const {computed, key, value} = properties[i];
|
||||||
const k = computed ? this.evaluate(key) : {value: key.name};
|
const k = computed ? this.evaluate(key) : {value: key.name};
|
||||||
const v = this.evaluate(value);
|
const v = this.evaluate(value);
|
||||||
|
@ -249,10 +252,50 @@ export default class Sandbox {
|
||||||
entries.push([k.value, v.value]);
|
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 {
|
return {
|
||||||
async: !!isAsync,
|
async: !!isAsync,
|
||||||
value: 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),
|
: Object.fromEntries(entries),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -335,13 +378,12 @@ export default class Sandbox {
|
||||||
this.parents = new WeakMap();
|
this.parents = new WeakMap();
|
||||||
this.scopes = new WeakMap();
|
this.scopes = new WeakMap();
|
||||||
this.runner = (function* traverse() {
|
this.runner = (function* traverse() {
|
||||||
yield* self.traverse(self.ast);
|
return yield* self.traverse(self.ast);
|
||||||
return undefined;
|
|
||||||
}());
|
}());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
rootScope() {
|
get rootScope() {
|
||||||
return this.nodeScope(this.ast);
|
return this.nodeScope(this.ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,11 +427,6 @@ export default class Sandbox {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext(context) {
|
|
||||||
this.setNodeScope(this.ast, new Scope(context || this.context()));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNextScope(node, scope = this.nodeScope(node)) {
|
setNextScope(node, scope = this.nodeScope(node)) {
|
||||||
const nodes = this.nextNodes(node, types.VISITOR_KEYS[node.type]);
|
const nodes = this.nextNodes(node, types.VISITOR_KEYS[node.type]);
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
@ -405,6 +442,44 @@ export default class Sandbox {
|
||||||
this.scopes.set(node, scope);
|
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) {
|
* traverse(node) {
|
||||||
let keys = types.VISITOR_KEYS[node.type];
|
let keys = types.VISITOR_KEYS[node.type];
|
||||||
if (!keys) {
|
if (!keys) {
|
||||||
|
@ -420,33 +495,47 @@ export default class Sandbox {
|
||||||
}
|
}
|
||||||
this.setNextScope(node, scope);
|
this.setNextScope(node, scope);
|
||||||
if (types.isVariableDeclarator(node)) {
|
if (types.isVariableDeclarator(node)) {
|
||||||
const {id, init} = node;
|
const {id} = node;
|
||||||
const {async, value} = this.evaluate(init);
|
const init = this.evaluate(node.init);
|
||||||
if (async) {
|
if (types.isIdentifier(id)) {
|
||||||
|
if (init.async) {
|
||||||
yield {
|
yield {
|
||||||
async: true,
|
async: true,
|
||||||
value: Promise.resolve(value).then((value) => {
|
value: Promise.resolve(init.value).then((value) => {
|
||||||
scope.allocate(id.name, value);
|
scope.allocate(id.name, value);
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
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...
|
// Blocks...
|
||||||
if (types.isIfStatement(node)) {
|
if (types.isIfStatement(node)) {
|
||||||
const {async, value} = this.evaluate(node.test);
|
const {async, value} = this.evaluate(node.test);
|
||||||
|
const branch = (value) => {
|
||||||
|
keys = [value ? 'consequent' : 'alternate'];
|
||||||
|
};
|
||||||
if (async) {
|
if (async) {
|
||||||
yield {
|
yield {
|
||||||
async: true,
|
async: true,
|
||||||
value: Promise.resolve(value).then((value) => {
|
value: Promise.resolve(value).then(branch),
|
||||||
keys = [value ? 'consequent' : 'alternate'];
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
keys = [value ? 'consequent' : 'alternate'];
|
branch(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Loops...
|
// Loops...
|
||||||
|
@ -480,7 +569,11 @@ export default class Sandbox {
|
||||||
// Recur...
|
// Recur...
|
||||||
const nodes = this.nextNodes(node, keys);
|
const nodes = this.nextNodes(node, keys);
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
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...
|
// Loops...
|
||||||
if (types.isForStatement(node)) {
|
if (types.isForStatement(node)) {
|
||||||
|
@ -520,6 +613,10 @@ export default class Sandbox {
|
||||||
scope = scope.pop();
|
scope = scope.pop();
|
||||||
}
|
}
|
||||||
// Evaluate...
|
// Evaluate...
|
||||||
|
if (types.isReturnStatement(node)) {
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
return this.evaluate(node.argument);
|
||||||
|
}
|
||||||
if (types.isDirective(node)) {
|
if (types.isDirective(node)) {
|
||||||
yield this.evaluate(node.value);
|
yield this.evaluate(node.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,18 @@ export default class Scope {
|
||||||
context = {};
|
context = {};
|
||||||
|
|
||||||
constructor(context = {}) {
|
constructor(context = {}) {
|
||||||
this.context = Object
|
this.context = context;
|
||||||
.fromEntries(
|
|
||||||
Object.entries(context)
|
|
||||||
.map(([key, value]) => [key, {value}]),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allocate(key, value) {
|
allocate(key, value) {
|
||||||
if (this.context[key]) {
|
this.context[key] = value;
|
||||||
this.context[key].value = value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.context[key] = {value};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
let walk = this;
|
let walk = this;
|
||||||
while (walk) {
|
while (walk) {
|
||||||
if (walk.context[key]) {
|
if (key in walk.context) {
|
||||||
return walk.context[key].value;
|
return walk.context[key];
|
||||||
}
|
}
|
||||||
walk = walk.parent;
|
walk = walk.parent;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +30,7 @@ export default class Scope {
|
||||||
while (walk) {
|
while (walk) {
|
||||||
const entries = Object.entries(walk.context);
|
const entries = Object.entries(walk.context);
|
||||||
for (let i = 0; i < entries.length; i++) {
|
for (let i = 0; i < entries.length; i++) {
|
||||||
const [key, {value}] = entries[i];
|
const [key, value] = entries[i];
|
||||||
if (!keys.has(key)) {
|
if (!keys.has(key)) {
|
||||||
keys.add(key);
|
keys.add(key);
|
||||||
all[key] = value;
|
all[key] = value;
|
||||||
|
@ -65,8 +56,8 @@ export default class Scope {
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
let walk = this;
|
let walk = this;
|
||||||
while (walk) {
|
while (walk) {
|
||||||
if (walk.context[key]) {
|
if (key in walk.context) {
|
||||||
walk.context[key].value = value;
|
walk.context[key] = value;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
walk = walk.parent;
|
walk = walk.parent;
|
||||||
|
|
|
@ -3,7 +3,52 @@ import {expect} from 'chai';
|
||||||
|
|
||||||
import Sandbox from '../src/sandbox';
|
import Sandbox from '../src/sandbox';
|
||||||
|
|
||||||
it('evaluates AssignmentExpression', () => {
|
it('evaluates async AssignmentExpression', async () => {
|
||||||
expect(new Sandbox(parse('let foo = 69; foo = 420')).next().value)
|
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});
|
.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', () => {
|
it('can update context', () => {
|
||||||
const sandbox = new Sandbox(parse('foo = 420'), {foo: 69});
|
const sandbox = new Sandbox(parse('foo = 420'), {foo: 69});
|
||||||
expect(sandbox.context())
|
expect(sandbox.context)
|
||||||
.to.deep.equal({foo: 69});
|
.to.deep.equal({foo: 69});
|
||||||
sandbox.run();
|
sandbox.run();
|
||||||
expect(sandbox.context())
|
expect(sandbox.context)
|
||||||
.to.deep.equal({foo: 420});
|
.to.deep.equal({foo: 420});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,38 @@ import {expect} from 'chai';
|
||||||
|
|
||||||
import Sandbox from '../src/sandbox';
|
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', () => {
|
it('evaluates ObjectExpression', () => {
|
||||||
expect(new Sandbox(parse('({a: 1, b: 2})')).next().value)
|
expect(new Sandbox(parse('({a: 1, b: 2})')).next().value)
|
||||||
.to.deep.include({value: {a: 1, b: 2}});
|
.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