chore: coverage and tidy

This commit is contained in:
cha0s 2024-06-30 14:20:37 -05:00
parent d00bcf23f3
commit 2ab82d1d3e
10 changed files with 73 additions and 169 deletions

View File

@ -41,6 +41,7 @@ export default function evaluate(node, {scope} = {}) {
return evaluators.unary(node, {evaluate, scope}); return evaluators.unary(node, {evaluate, scope});
case 'UpdateExpression': case 'UpdateExpression':
return evaluators.update(node, {evaluate, scope}); return evaluators.update(node, {evaluate, scope});
/* v8 ignore next 2 */
default: default:
throw new EvalError(`astride: Can't evaluate node of type ${node.type}`) throw new EvalError(`astride: Can't evaluate node of type ${node.type}`)
} }

View File

@ -1,12 +1,10 @@
import {isSpreadElement} from '@/astride/types.js';
export default function(node, {evaluate, scope}) { export default function(node, {evaluate, scope}) {
const elements = []; const elements = [];
const asyncSpread = Object.create(null); const asyncSpread = Object.create(null);
let isAsync = false; let isAsync = false;
for (const index in node.elements) { for (const index in node.elements) {
const element = node.elements[index]; const element = node.elements[index];
if (isSpreadElement(element)) { if ('SpreadElement' === element.type) {
const {async, value} = evaluate(element.argument, {scope}); const {async, value} = evaluate(element.argument, {scope});
isAsync = isAsync || async; isAsync = isAsync || async;
if (async) { if (async) {

View File

@ -1,11 +1,7 @@
import {
isMemberExpression,
} from '@/astride/types.js';
export default function(node, {evaluate, scope}) { export default function(node, {evaluate, scope}) {
const {operator, left} = node; const {operator, left} = node;
const right = evaluate(node.right, {scope}); const right = evaluate(node.right, {scope});
if (!isMemberExpression(left)) { if (!('MemberExpression' === left.type)) {
const assign = (value) => { const assign = (value) => {
switch (operator) { switch (operator) {
case '=' : return scope.set(left.name, value); case '=' : return scope.set(left.name, value);

View File

@ -1,7 +1,4 @@
import fastCall from '@/util/fast-call.js'; import fastCall from '@/util/fast-call.js';
import {
isMemberExpression,
} from '@/astride/types.js';
export default function(node, {evaluate, scope}) { export default function(node, {evaluate, scope}) {
let asyncArgs = false; let asyncArgs = false;
@ -24,7 +21,7 @@ export default function(node, {evaluate, scope}) {
} }
return fastCall(fn, holder, args); return fastCall(fn, holder, args);
}; };
if (!isMemberExpression(callee)) { if (!('MemberExpression' === callee.type)) {
const {async, value} = evaluate(callee, {scope}); const {async, value} = evaluate(callee, {scope});
if (asyncArgs || async) { if (asyncArgs || async) {
return { return {

View File

@ -48,10 +48,8 @@ scopeTest('evaluates optional calls', async ({scope}) => {
scope.set('O', {}); scope.set('O', {});
expect(evaluate(await expression('g?.(1, 2, 3)'), {scope}).value) expect(evaluate(await expression('g?.(1, 2, 3)'), {scope}).value)
.to.equal(undefined); .to.equal(undefined);
// expect(evaluate(await expression('O?.g(1, 2, 3)'), {scope}).value) expect(evaluate(await expression('O?.g?.(1, 2, 3)'), {scope}).value)
// .to.equal(undefined); .to.equal(undefined);
// expect(evaluate(await expression('O?.g?.(1, 2, 3)'), {scope}).value)
// .to.equal(undefined);
}); });
scopeTest('evaluates async calls', async ({scope}) => { scopeTest('evaluates async calls', async ({scope}) => {

View File

@ -1,25 +1,18 @@
import {
isIdentifier,
isLiteral,
isProperty,
isSpreadElement,
} from '@/astride/types.js';
export default function(node, {evaluate, scope}) { export default function(node, {evaluate, scope}) {
const {properties} = node; const {properties} = node;
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 (isProperty(properties[i])) { if ('Property' === properties[i].type) {
const {computed, key, value} = properties[i]; const {computed, key, value} = properties[i];
let k; let k;
if (computed) { if (computed) {
k = evaluate(key, {scope}); k = evaluate(key, {scope});
} }
else if (isIdentifier(key)) { else if ('Identifier' === key.type) {
k = {value: key.name}; k = {value: key.name};
} }
else if (isLiteral(key)) { else if ('Literal' === key.type) {
k = {value: key.value}; k = {value: key.value};
} }
/* v8 ignore next 3 */ /* v8 ignore next 3 */
@ -35,7 +28,7 @@ export default function(node, {evaluate, scope}) {
entries.push([k.value, v.value]); entries.push([k.value, v.value]);
} }
} }
if (isSpreadElement(properties[i])) { if ('SpreadElement' === properties[i].type) {
const {argument} = properties[i]; const {argument} = properties[i];
const spreading = evaluate(argument, {scope}); const spreading = evaluate(argument, {scope});
isAsync ||= spreading.async; isAsync ||= spreading.async;

View File

@ -1,13 +1,6 @@
import evaluate from '@/astride/evaluate.js'; import evaluate from '@/astride/evaluate.js';
import Scope from '@/astride/scope.js'; import Scope from '@/astride/scope.js';
import traverse from '@/astride/traverse.js'; import traverse from '@/astride/traverse.js';
import {
isArrayPattern,
isBlockStatement,
isForStatement,
isIdentifier,
isObjectPattern,
} from '@/astride/types.js';
const YIELD_NONE = 0; const YIELD_NONE = 0;
const YIELD_PROMISE = 1; const YIELD_PROMISE = 1;
@ -34,8 +27,8 @@ export default class Sandbox {
this.ast, this.ast,
(node, verb) => { (node, verb) => {
if ( if (
isBlockStatement(node) 'BlockStatement' === node.type
|| isForStatement(node) || 'ForStatement' === node.type
) { ) {
switch (verb) { switch (verb) {
case 'enter': { case 'enter': {
@ -67,7 +60,7 @@ export default class Sandbox {
if (null === element) { if (null === element) {
continue; continue;
} }
if (isIdentifier(element)) { if ('Identifier' === element.type) {
scope.allocate(element.name, init[i]); scope.allocate(element.name, init[i]);
} }
/* v8 ignore next 3 */ /* v8 ignore next 3 */
@ -83,7 +76,7 @@ export default class Sandbox {
const {properties} = id; const {properties} = id;
for (let i = 0; i < properties.length; ++i) { for (let i = 0; i < properties.length; ++i) {
const property = properties[i]; const property = properties[i];
if (isObjectPattern(property.value)) { if ('ObjectPattern' === property.value.type) {
this.destructureObject(property.value, init[property.key.name]); this.destructureObject(property.value, init[property.key.name]);
} }
else { else {
@ -135,6 +128,7 @@ export default class Sandbox {
case 'ChainExpression': case 'ChainExpression':
case 'ObjectExpression': case 'ObjectExpression':
case 'Identifier': case 'Identifier':
case 'LogicalExpression':
case 'MemberExpression': case 'MemberExpression':
case 'UnaryExpression': case 'UnaryExpression':
case 'UpdateExpression': { case 'UpdateExpression': {
@ -156,6 +150,7 @@ export default class Sandbox {
if (result.yield) { if (result.yield) {
return result; return result;
} }
/* v8 ignore next 2 */
break; break;
} }
case 'BlockStatement': { case 'BlockStatement': {
@ -168,6 +163,7 @@ export default class Sandbox {
if (skipping && child === this.$$execution.stack[depth + 1]) { if (skipping && child === this.$$execution.stack[depth + 1]) {
skipping = false; skipping = false;
} }
/* v8 ignore next 3 */
if (skipping) { if (skipping) {
continue; continue;
} }
@ -370,6 +366,7 @@ export default class Sandbox {
if (skipping && child === this.$$execution.stack[depth + 1]) { if (skipping && child === this.$$execution.stack[depth + 1]) {
skipping = false; skipping = false;
} }
/* v8 ignore next 3 */
if (skipping) { if (skipping) {
continue; continue;
} }
@ -390,7 +387,7 @@ export default class Sandbox {
} }
else { else {
const init = this.executeSync(node.init, depth + 1); const init = this.executeSync(node.init, depth + 1);
if (isIdentifier(id)) { if ('Identifier' === id.type) {
if (init.yield) { if (init.yield) {
return { return {
value: Promise.resolve(init.value) value: Promise.resolve(init.value)
@ -405,7 +402,7 @@ export default class Sandbox {
}; };
} }
} }
else if (isArrayPattern(id)) { else if ('ArrayPattern' === id.type) {
if (init.yield) { if (init.yield) {
return { return {
value: Promise.resolve(init.value) value: Promise.resolve(init.value)
@ -420,7 +417,7 @@ export default class Sandbox {
}; };
} }
} }
else if (isObjectPattern(id)) { else if ('ObjectPattern' === id.type) {
if (init.yield) { if (init.yield) {
return { return {
value: Promise.resolve(init.value) value: Promise.resolve(init.value)
@ -475,6 +472,7 @@ export default class Sandbox {
yield: YIELD_LOOP_UPDATE, yield: YIELD_LOOP_UPDATE,
}; };
} }
/* v8 ignore next 7 */
default: default:
console.log( console.log(
node.type, node.type,

View File

@ -147,6 +147,33 @@ test('evaluates conditional branches', async () => {
}); });
}); });
test('evaluates conditional expressions', async () => {
const sandbox = new Sandbox(
await parse(`
const x = true || false ? 1 : 2
const y = false && true ? 1 : 2
const a = (await true) ? await 1 : await 2
const b = (await false) ? await 1 : await 2
xx = true || false ? 1 : 2
yy = false && true ? 1 : 2
aa = (await true) ? await 1 : await 2
bb = (await false) ? await 1 : await 2
`),
);
await finish(sandbox);
expect(sandbox.context)
.to.deep.equal({
x: 1,
y: 2,
a: 1,
b: 2,
xx: 1,
yy: 2,
aa: 1,
bb: 2,
});
});
test('evaluates loops', async () => { test('evaluates loops', async () => {
const sandbox = new Sandbox( const sandbox = new Sandbox(
await parse(` await parse(`
@ -155,6 +182,7 @@ test('evaluates loops', async () => {
a = 0 a = 0
b = 0 b = 0
c = 0 c = 0
d = 0
for (let i = 0; i < 3; ++i) { for (let i = 0; i < 3; ++i) {
x += 1; x += 1;
} }
@ -165,11 +193,14 @@ test('evaluates loops', async () => {
a += 1; a += 1;
} while (a < 3); } while (a < 3);
do { do {
b += 1; b += await 1;
} while (await b < 3); } while (await b < 3);
while (c < 3) { while (c < 3) {
c += 1; c += 1;
} }
while (await d < 3) {
d += await 1;
}
`), `),
); );
await finish(sandbox); await finish(sandbox);
@ -178,6 +209,7 @@ test('evaluates loops', async () => {
a: 3, a: 3,
b: 3, b: 3,
c: 3, c: 3,
d: 3,
x: 3, x: 3,
y: 3, y: 3,
}); });
@ -207,8 +239,20 @@ test('returns values at the top level', async () => {
y = 8 y = 8
`, {allowReturnOutsideFunction: true}), `, {allowReturnOutsideFunction: true}),
); );
const {value} = sandbox.run(); expect(sandbox.run().value)
expect(value) .to.deep.equal([48, 12]);
expect(sandbox.context)
.to.deep.equal({x: 16, y: 4});
sandbox = new Sandbox(
await parse(`
x = 16
y = 4
return await [x * 3, y * 3]
x = 32
y = 8
`, {allowReturnOutsideFunction: true}),
);
expect((await finish(sandbox)).value)
.to.deep.equal([48, 12]); .to.deep.equal([48, 12]);
expect(sandbox.context) expect(sandbox.context)
.to.deep.equal({x: 16, y: 4}); .to.deep.equal({x: 16, y: 4});

View File

@ -15,6 +15,7 @@ export const TRAVERSAL_PATH = {
IfStatement: ['alternate', 'consequent', 'test'], IfStatement: ['alternate', 'consequent', 'test'],
MemberExpression: ['object', 'property'], MemberExpression: ['object', 'property'],
Literal: [], Literal: [],
LogicalExpression: ['left', 'right'],
ObjectExpression: ['properties'], ObjectExpression: ['properties'],
ObjectPattern: ['properties'], ObjectPattern: ['properties'],
Program: ['body'], Program: ['body'],
@ -35,15 +36,9 @@ export default function traverse(node, visitor) {
} }
visitor(node, 'enter'); visitor(node, 'enter');
const path = TRAVERSAL_PATH[node.type]; const path = TRAVERSAL_PATH[node.type];
let children; const children = [];
if (path instanceof Function) { for (const key of path) {
children = path(node); children.push(...(Array.isArray(node[key]) ? node[key] : [node[key]]));
}
else if (Array.isArray(path)) {
children = [];
for (const key of path) {
children.push(...(Array.isArray(node[key]) ? node[key] : [node[key]]));
}
} }
for (const child of children) { for (const child of children) {
if (child) { if (child) {

View File

@ -1,116 +0,0 @@
export function isArrayPattern(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'ArrayPattern') {
return false;
}
return true;
}
export function isBlockStatement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'BlockStatement') {
return false;
}
return true;
}
export function isDoWhileStatement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'DoWhileStatement') {
return false;
}
return true;
}
export function isExpressionStatement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'ExpressionStatement') {
return false;
}
return true;
}
export function isForStatement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'ForStatement') {
return false;
}
return true;
}
export function isIdentifier(node) {
if (!node || node.type !== 'Identifier') {
return false;
}
return true;
}
export function isIfStatement(node) {
if (!node || node.type !== 'IfStatement') {
return false;
}
return true;
}
export function isLiteral(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'Literal') {
return false;
}
return true;
}
export function isMemberExpression(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'MemberExpression') {
return false;
}
return true;
}
export function isObjectPattern(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'ObjectPattern') {
return false;
}
return true;
}
export function isProperty(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'Property') {
return false;
}
return true;
}
export function isReturnStatement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'ReturnStatement') {
return false;
}
return true;
}
export function isSpreadElement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'SpreadElement') {
return false;
}
return true;
}
export function isVariableDeclarator(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'VariableDeclarator') {
return false;
}
return true;
}
export function isWhileStatement(node) {
if (!node || node.type !== 'WhileStatement') {
return false;
}
return true;
}