feat: initial swcx
This commit is contained in:
parent
ebb34eef95
commit
2f93e42106
|
@ -28,6 +28,14 @@ module.exports = {
|
|||
extends: ['eslint:recommended'],
|
||||
|
||||
overrides: [
|
||||
// Tests
|
||||
{
|
||||
files: ['**/*.test.{js,jsx,ts,tsx}'],
|
||||
rules: {
|
||||
'no-empty-pattern': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// React
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,4 +3,5 @@ node_modules
|
|||
/app/data
|
||||
/.cache
|
||||
/build
|
||||
/coverage
|
||||
.env
|
||||
|
|
5
app/swcx/builders.js
Normal file
5
app/swcx/builders.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export async function first(code) {
|
||||
const {parse} = await import('@swc/core');
|
||||
const ast = await parse(code);
|
||||
return ast.body[0].expression;
|
||||
}
|
47
app/swcx/evaluate.js
Normal file
47
app/swcx/evaluate.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import {unwrap} from '@/swcx/types.js';
|
||||
|
||||
const evaluators = Object.fromEntries(
|
||||
Object.entries(
|
||||
import.meta.glob(
|
||||
['./evaluators/*.js', '!./evaluators/*.test.js'],
|
||||
{eager: true, import: 'default'},
|
||||
)
|
||||
)
|
||||
.map(([path, evaluator]) => ([
|
||||
path.replace(/\.\/evaluators\/(.*)\.js/, '$1'),
|
||||
evaluator,
|
||||
])),
|
||||
);
|
||||
|
||||
export default function evaluate(node, {scope} = {}) {
|
||||
const unwrapped = unwrap(node);
|
||||
switch (unwrapped.type) {
|
||||
case 'ArrayExpression':
|
||||
return evaluators.array(unwrapped, {evaluate, scope});
|
||||
case 'AssignmentExpression':
|
||||
return evaluators.assignment(unwrapped, {evaluate, scope});
|
||||
case 'AwaitExpression':
|
||||
return evaluators.await(unwrapped, {evaluate, scope});
|
||||
case 'BinaryExpression':
|
||||
return evaluators.binary(unwrapped, {evaluate, scope});
|
||||
case 'BooleanLiteral':
|
||||
case 'NullLiteral':
|
||||
case 'NumericLiteral':
|
||||
case 'StringLiteral':
|
||||
return evaluators.literal(unwrapped, {evaluate, scope});
|
||||
case 'CallExpression':
|
||||
return evaluators.call(unwrapped, {evaluate, scope});
|
||||
case 'ConditionalExpression':
|
||||
return evaluators.conditional(unwrapped, {evaluate, scope});
|
||||
case 'Identifier':
|
||||
return evaluators.identifier(unwrapped, {evaluate, scope});
|
||||
case 'MemberExpression':
|
||||
return evaluators.member(unwrapped, {evaluate, scope});
|
||||
case 'ObjectExpression':
|
||||
return evaluators.object(unwrapped, {evaluate, scope});
|
||||
case 'UnaryExpression':
|
||||
return evaluators.unary(unwrapped, {evaluate, scope});
|
||||
case 'UpdateExpression':
|
||||
return evaluators.update(unwrapped, {evaluate, scope});
|
||||
}
|
||||
}
|
13
app/swcx/evaluators/array.js
Normal file
13
app/swcx/evaluators/array.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default function(node, {evaluate, scope}) {
|
||||
const elements = [];
|
||||
let isAsync = false;
|
||||
for (const {expression} of node.elements) {
|
||||
const {async, value} = evaluate(expression, {scope});
|
||||
isAsync = isAsync || async;
|
||||
elements.push(value);
|
||||
}
|
||||
return {
|
||||
async: !!isAsync,
|
||||
value: isAsync ? Promise.all(elements) : elements,
|
||||
};
|
||||
}
|
17
app/swcx/evaluators/array.test.js
Normal file
17
app/swcx/evaluators/array.test.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
test('evaluates array of literals', async () => {
|
||||
expect(evaluate(await first('[1.5, 2, "three"]')))
|
||||
.to.deep.include({value: [1.5, 2, 'three']});
|
||||
});
|
||||
|
||||
test('evaluates array containing promises', async () => {
|
||||
const evaluated = evaluate(await first('[1.5, 2, await "three"]'));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.deep.equal([1.5, 2, 'three']);
|
||||
});
|
84
app/swcx/evaluators/assignment.js
Normal file
84
app/swcx/evaluators/assignment.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
import {
|
||||
isComputed,
|
||||
isMemberExpression,
|
||||
} from '@/swcx/types.js';
|
||||
|
||||
export default function(node, {evaluate, scope}) {
|
||||
const {operator, left} = node;
|
||||
const right = evaluate(node.right, {scope});
|
||||
if (!isMemberExpression(left)) {
|
||||
const assign = (value) => {
|
||||
switch (operator) {
|
||||
case '=' : return scope.set(left.value, value);
|
||||
case '+=' : return scope.set(left.value, scope.get(left.value) + value);
|
||||
case '-=' : return scope.set(left.value, scope.get(left.value) - value);
|
||||
case '*=' : return scope.set(left.value, scope.get(left.value) * value);
|
||||
case '/=' : return scope.set(left.value, scope.get(left.value) / value);
|
||||
case '%=' : return scope.set(left.value, scope.get(left.value) % value);
|
||||
case '**=' : return scope.set(left.value, scope.get(left.value) ** value);
|
||||
case '<<=' : return scope.set(left.value, scope.get(left.value) << value);
|
||||
case '>>=' : return scope.set(left.value, scope.get(left.value) >> value);
|
||||
case '>>>=': return scope.set(left.value, scope.get(left.value) >>> value);
|
||||
case '|=' : return scope.set(left.value, scope.get(left.value) | value);
|
||||
case '^=' : return scope.set(left.value, scope.get(left.value) ^ value);
|
||||
case '&=' : return scope.set(left.value, scope.get(left.value) & value);
|
||||
case '||=' : return scope.set(left.value, scope.get(left.value) || value);
|
||||
case '&&=' : return scope.set(left.value, scope.get(left.value) && value);
|
||||
case '??=' : {
|
||||
const l = scope.get(left.value);
|
||||
return scope.set(left.value, (l === null || l === undefined) ? value : l);
|
||||
}
|
||||
/* v8 ignore next 2 */
|
||||
default:
|
||||
throw new Error(`operator not implemented: ${node.operator}`);
|
||||
}
|
||||
};
|
||||
if (right.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: Promise.resolve(right.value).then(assign),
|
||||
};
|
||||
}
|
||||
return {value: assign(right.value)};
|
||||
}
|
||||
const {
|
||||
object,
|
||||
property,
|
||||
} = left;
|
||||
const memberAssign = (O, P, value) => {
|
||||
switch (operator) {
|
||||
case '=' : return O[P] = value;
|
||||
case '+=' : return O[P] += value;
|
||||
case '-=' : return O[P] -= value;
|
||||
case '*=' : return O[P] *= value;
|
||||
case '/=' : return O[P] /= value;
|
||||
case '%=' : return O[P] %= value;
|
||||
case '**=' : return O[P] **= value;
|
||||
case '<<=' : return O[P] <<= value;
|
||||
case '>>=' : return O[P] >>= value;
|
||||
case '>>>=': return O[P] >>>= value;
|
||||
case '|=' : return O[P] |= value;
|
||||
case '^=' : return O[P] ^= value;
|
||||
case '&=' : return O[P] &= value;
|
||||
case '||=' : return O[P] ||= value;
|
||||
case '&&=' : return O[P] &&= value;
|
||||
case '??=' : return O[P] = (O[P] === null || O[P] === undefined) ? value : O[P];
|
||||
/* v8 ignore next 2 */
|
||||
default:
|
||||
throw new Error(`operator not implemented: ${node.operator}`);
|
||||
}
|
||||
};
|
||||
const makeAsync = (O, P, value) => (
|
||||
Promise.all([O, P, value]).then(([O, P, value]) => memberAssign(O, P, value))
|
||||
);
|
||||
const O = evaluate(object, {scope});
|
||||
const P = isComputed(property) ? evaluate(property, {scope}) : {value: property.value};
|
||||
if (right.async || O.async || P.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: makeAsync(O.value, P.value, right.value),
|
||||
};
|
||||
}
|
||||
return {value: memberAssign(O.value, P.value, right.value)};
|
||||
}
|
||||
|
267
app/swcx/evaluators/assignment.test.js
Normal file
267
app/swcx/evaluators/assignment.test.js
Normal file
|
@ -0,0 +1,267 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
const scopeTest = test.extend({
|
||||
scope: async ({}, use) => {
|
||||
await use({
|
||||
S: {O: {}},
|
||||
get(k) { return this.S[k]; },
|
||||
set(k, v) { return this.S[k] = v; }
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
scopeTest('evaluates =', async ({scope}) => {
|
||||
expect(evaluate(await first('x = 4'), {scope}))
|
||||
.to.deep.include({value: 4});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(4);
|
||||
expect(evaluate(await first('O.x = 4'), {scope}))
|
||||
.to.deep.include({value: 4});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(4);
|
||||
});
|
||||
|
||||
scopeTest('evaluates +=', async ({scope}) => {
|
||||
scope.set('x', 1);
|
||||
expect(evaluate(await first('x += 4'), {scope}))
|
||||
.to.deep.include({value: 5});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(5);
|
||||
scope.set('O', {x: 1});
|
||||
expect(evaluate(await first('O.x += 4'), {scope}))
|
||||
.to.deep.include({value: 5});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(5);
|
||||
});
|
||||
|
||||
scopeTest('evaluates -=', async ({scope}) => {
|
||||
scope.set('x', 5);
|
||||
expect(evaluate(await first('x -= 4'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(1);
|
||||
scope.set('O', {x: 5});
|
||||
expect(evaluate(await first('O.x -= 4'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
scopeTest('evaluates *=', async ({scope}) => {
|
||||
scope.set('x', 5);
|
||||
expect(evaluate(await first('x *= 4'), {scope}))
|
||||
.to.deep.include({value: 20});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(20);
|
||||
scope.set('O', {x: 5});
|
||||
expect(evaluate(await first('O.x *= 4'), {scope}))
|
||||
.to.deep.include({value: 20});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(20);
|
||||
});
|
||||
|
||||
scopeTest('evaluates /=', async ({scope}) => {
|
||||
scope.set('x', 25);
|
||||
expect(evaluate(await first('x /= 5'), {scope}))
|
||||
.to.deep.include({value: 5});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(5);
|
||||
scope.set('O', {x: 25});
|
||||
expect(evaluate(await first('O.x /= 5'), {scope}))
|
||||
.to.deep.include({value: 5});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(5);
|
||||
});
|
||||
|
||||
scopeTest('evaluates %=', async ({scope}) => {
|
||||
scope.set('x', 5);
|
||||
expect(evaluate(await first('x %= 2'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(1);
|
||||
scope.set('O', {x: 5});
|
||||
expect(evaluate(await first('O.x %= 2'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
scopeTest('evaluates **=', async ({scope}) => {
|
||||
scope.set('x', 5);
|
||||
expect(evaluate(await first('x **= 3'), {scope}))
|
||||
.to.deep.include({value: 125});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(125);
|
||||
scope.set('O', {x: 5});
|
||||
expect(evaluate(await first('O.x **= 3'), {scope}))
|
||||
.to.deep.include({value: 125});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(125);
|
||||
});
|
||||
|
||||
scopeTest('evaluates <<=', async ({scope}) => {
|
||||
scope.set('x', 2);
|
||||
expect(evaluate(await first('x <<= 1'), {scope}))
|
||||
.to.deep.include({value: 4});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(4);
|
||||
scope.set('O', {x: 2});
|
||||
expect(evaluate(await first('O.x <<= 1'), {scope}))
|
||||
.to.deep.include({value: 4});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(4);
|
||||
});
|
||||
|
||||
scopeTest('evaluates >>=', async ({scope}) => {
|
||||
scope.set('x', 8);
|
||||
expect(evaluate(await first('x >>= 2'), {scope}))
|
||||
.to.deep.include({value: 2});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(2);
|
||||
scope.set('O', {x: 8});
|
||||
expect(evaluate(await first('O.x >>= 2'), {scope}))
|
||||
.to.deep.include({value: 2});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(2);
|
||||
});
|
||||
|
||||
scopeTest('evaluates >>>=', async ({scope}) => {
|
||||
scope.set('x', -1);
|
||||
expect(evaluate(await first('x >>>= 1'), {scope}))
|
||||
.to.deep.include({value: 2147483647});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(2147483647);
|
||||
scope.set('O', {x: -1});
|
||||
expect(evaluate(await first('O.x >>>= 1'), {scope}))
|
||||
.to.deep.include({value: 2147483647});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(2147483647);
|
||||
});
|
||||
|
||||
scopeTest('evaluates |=', async ({scope}) => {
|
||||
scope.set('x', 3);
|
||||
expect(evaluate(await first('x |= 5'), {scope}))
|
||||
.to.deep.include({value: 7});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(7);
|
||||
scope.set('O', {x: 3});
|
||||
expect(evaluate(await first('O.x |= 5'), {scope}))
|
||||
.to.deep.include({value: 7});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(7);
|
||||
});
|
||||
|
||||
scopeTest('evaluates ^=', async ({scope}) => {
|
||||
scope.set('x', 7);
|
||||
expect(evaluate(await first('x ^= 2'), {scope}))
|
||||
.to.deep.include({value: 5});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(5);
|
||||
scope.set('O', {x: 7});
|
||||
expect(evaluate(await first('O.x ^= 2'), {scope}))
|
||||
.to.deep.include({value: 5});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(5);
|
||||
});
|
||||
|
||||
scopeTest('evaluates &=', async ({scope}) => {
|
||||
scope.set('x', 5);
|
||||
expect(evaluate(await first('x &= 3'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(1);
|
||||
scope.set('O', {x: 5});
|
||||
expect(evaluate(await first('O.x &= 3'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
scopeTest('evaluates ||=', async ({scope}) => {
|
||||
scope.set('x', false);
|
||||
expect(evaluate(await first('x ||= true'), {scope}))
|
||||
.to.deep.include({value: true});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(true);
|
||||
scope.set('O', {x: false});
|
||||
expect(evaluate(await first('O.x ||= true'), {scope}))
|
||||
.to.deep.include({value: true});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(true);
|
||||
});
|
||||
|
||||
scopeTest('evaluates &&=', async ({scope}) => {
|
||||
scope.set('x', true);
|
||||
expect(evaluate(await first('x &&= true'), {scope}))
|
||||
.to.deep.include({value: true});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(true);
|
||||
expect(evaluate(await first('x &&= false'), {scope}))
|
||||
.to.deep.include({value: false});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(false);
|
||||
scope.set('O', {x: true});
|
||||
expect(evaluate(await first('O.x &&= true'), {scope}))
|
||||
.to.deep.include({value: true});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(true);
|
||||
expect(evaluate(await first('O.x &&= false'), {scope}))
|
||||
.to.deep.include({value: false});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(false);
|
||||
});
|
||||
|
||||
scopeTest('evaluates ??=', async ({scope}) => {
|
||||
scope.set('x', null);
|
||||
expect(evaluate(await first('x ??= 2'), {scope}))
|
||||
.to.deep.include({value: 2});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(2);
|
||||
expect(evaluate(await first('x ??= 4'), {scope}))
|
||||
.to.deep.include({value: 2});
|
||||
expect(scope.get('x'))
|
||||
.to.equal(2);
|
||||
scope.set('O', {x: null});
|
||||
expect(evaluate(await first('O.x ??= 2'), {scope}))
|
||||
.to.deep.include({value: 2});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(2);
|
||||
expect(evaluate(await first('O.x ??= 4'), {scope}))
|
||||
.to.deep.include({value: 2});
|
||||
expect(scope.get('O').x)
|
||||
.to.equal(2);
|
||||
});
|
||||
|
||||
scopeTest('evaluates promised assignment', async ({scope}) => {
|
||||
const evaluated = evaluate(await first('x = await 4'), {scope});
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(4);
|
||||
expect(await scope.get('x'))
|
||||
.to.equal(4);
|
||||
const evaluatedComputedObject = evaluate(await first('O["x"] = await 4'), {scope});
|
||||
expect(evaluatedComputedObject.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluatedComputedObject.value)
|
||||
.to.equal(4);
|
||||
expect(await scope.get('O').x)
|
||||
.to.equal(4);
|
||||
const evaluatedObject = evaluate(await first('O.x = await 4'), {scope});
|
||||
expect(evaluatedObject.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluatedObject.value)
|
||||
.to.equal(4);
|
||||
expect(await scope.get('O').x)
|
||||
.to.equal(4);
|
||||
const evaluatedPromisedObject = evaluate(await first('(await O).x = await 4'), {scope});
|
||||
expect(evaluatedPromisedObject.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluatedPromisedObject.value)
|
||||
.to.equal(4);
|
||||
expect(await scope.get('O').x)
|
||||
.to.equal(4);
|
||||
});
|
7
app/swcx/evaluators/await.js
Normal file
7
app/swcx/evaluators/await.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default function(node, {evaluate, scope}) {
|
||||
const {value} = evaluate(node.argument, {scope});
|
||||
return {
|
||||
async: true,
|
||||
value: value instanceof Promise ? value : Promise.resolve(value),
|
||||
};
|
||||
}
|
20
app/swcx/evaluators/await.test.js
Normal file
20
app/swcx/evaluators/await.test.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
test('evaluates await expressions', async () => {
|
||||
const evaluated = evaluate(await first('await 1'));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
test('coalesces promises', async () => {
|
||||
const evaluated = evaluate(await first('await await await 1'));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(1);
|
||||
});
|
45
app/swcx/evaluators/binary.js
Normal file
45
app/swcx/evaluators/binary.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
export default function(node, {evaluate, scope}) {
|
||||
const binary = (left, right) => {
|
||||
switch (node.operator) {
|
||||
case '+' : return left + right;
|
||||
case '-' : return left - right;
|
||||
case '/' : return left / right;
|
||||
case '%' : return left % right;
|
||||
case '*' : return left * right;
|
||||
case '>' : return left > right;
|
||||
case '<' : return left < right;
|
||||
case 'in' : return left in right;
|
||||
case '>=' : return left >= right;
|
||||
case '<=' : return left <= right;
|
||||
case '**' : return left ** right;
|
||||
case '===': return left === right;
|
||||
case '!==': return left !== right;
|
||||
case '^' : return left ^ right;
|
||||
case '&' : return left & right;
|
||||
case '|' : return left | right;
|
||||
case '>>' : return left >> right;
|
||||
case '<<' : return left << right;
|
||||
case '>>>': return left >>> right;
|
||||
case '==' : return left == right;
|
||||
case '!=' : return left != right;
|
||||
case '||' : return left || right;
|
||||
case '&&' : return left && right;
|
||||
case '??' : return (left === null || left === undefined) ? right : left;
|
||||
case 'instanceof': return left instanceof right;
|
||||
/* v8 ignore next 2 */
|
||||
default:
|
||||
throw new Error(`operator not implemented: ${node.operator}`);
|
||||
}
|
||||
};
|
||||
const left = evaluate(node.left, {scope});
|
||||
const right = evaluate(node.right, {scope});
|
||||
if (left.async || right.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: Promise
|
||||
.all([left.value, right.value])
|
||||
.then(([left, right]) => binary(left, right)),
|
||||
};
|
||||
}
|
||||
return {value: binary(left.value, right.value)};
|
||||
}
|
177
app/swcx/evaluators/binary.test.js
Normal file
177
app/swcx/evaluators/binary.test.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
test('evaluates +', async () => {
|
||||
expect(evaluate(await first('10 + 2')))
|
||||
.to.deep.include({value: 12});
|
||||
});
|
||||
|
||||
test('evaluates -', async () => {
|
||||
expect(evaluate(await first('10 - 2')))
|
||||
.to.deep.include({value: 8});
|
||||
});
|
||||
|
||||
test('evaluates /', async () => {
|
||||
expect(evaluate(await first('10 / 2')))
|
||||
.to.deep.include({value: 5});
|
||||
});
|
||||
|
||||
test('evaluates %', async () => {
|
||||
expect(evaluate(await first('10 % 3')))
|
||||
.to.deep.include({value: 1});
|
||||
});
|
||||
|
||||
test('evaluates *', async () => {
|
||||
expect(evaluate(await first('10 * 2')))
|
||||
.to.deep.include({value: 20});
|
||||
});
|
||||
|
||||
test('evaluates >', async () => {
|
||||
expect(evaluate(await first('10 > 2')))
|
||||
.to.deep.include({value: true});
|
||||
});
|
||||
|
||||
test('evaluates <', async () => {
|
||||
expect(evaluate(await first('10 < 2')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates in', async () => {
|
||||
expect(evaluate(await first('"i" in {i: 69}')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('"j" in {i: 69}')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates >=', async () => {
|
||||
expect(evaluate(await first('10 >= 2')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('2 >= 2')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('1 >= 2')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates <=', async () => {
|
||||
expect(evaluate(await first('10 <= 2')))
|
||||
.to.deep.include({value: false});
|
||||
expect(evaluate(await first('2 <= 2')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('1 <= 2')))
|
||||
.to.deep.include({value: true});
|
||||
});
|
||||
|
||||
test('evaluates **', async () => {
|
||||
expect(evaluate(await first('2 ** 16')))
|
||||
.to.deep.include({value: 65536});
|
||||
});
|
||||
|
||||
test('evaluates ===', async () => {
|
||||
expect(evaluate(await first('10 === "10"')))
|
||||
.to.deep.include({value: false});
|
||||
expect(evaluate(await first('10 === 10')))
|
||||
.to.deep.include({value: true});
|
||||
});
|
||||
|
||||
test('evaluates !==', async () => {
|
||||
expect(evaluate(await first('10 !== "10"')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('10 !== 10')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates ^', async () => {
|
||||
expect(evaluate(await first('7 ^ 2')))
|
||||
.to.deep.include({value: 5});
|
||||
});
|
||||
|
||||
test('evaluates &', async () => {
|
||||
expect(evaluate(await first('5 & 3')))
|
||||
.to.deep.include({value: 1});
|
||||
});
|
||||
|
||||
test('evaluates |', async () => {
|
||||
expect(evaluate(await first('1 | 2')))
|
||||
.to.deep.include({value: 3});
|
||||
});
|
||||
|
||||
test('evaluates >>', async () => {
|
||||
expect(evaluate(await first('8 >> 1')))
|
||||
.to.deep.include({value: 4});
|
||||
});
|
||||
|
||||
test('evaluates <<', async () => {
|
||||
expect(evaluate(await first('2 << 1')))
|
||||
.to.deep.include({value: 4});
|
||||
});
|
||||
|
||||
test('evaluates >>>', async () => {
|
||||
expect(evaluate(await first('-1 >>> 1')))
|
||||
.to.deep.include({value: 2147483647});
|
||||
});
|
||||
|
||||
test('evaluates ==', async () => {
|
||||
expect(evaluate(await first('10 == "10"')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('10 == 10')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('10 == "ten"')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates !=', async () => {
|
||||
expect(evaluate(await first('10 != "10"')))
|
||||
.to.deep.include({value: false});
|
||||
expect(evaluate(await first('10 != 10')))
|
||||
.to.deep.include({value: false});
|
||||
expect(evaluate(await first('10 != "ten"')))
|
||||
.to.deep.include({value: true});
|
||||
});
|
||||
|
||||
test('evaluates ||', async () => {
|
||||
expect(evaluate(await first('true || true')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('true || false')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('false || false')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates &&', async () => {
|
||||
expect(evaluate(await first('true && true')))
|
||||
.to.deep.include({value: true});
|
||||
expect(evaluate(await first('true && false')))
|
||||
.to.deep.include({value: false});
|
||||
expect(evaluate(await first('false && false')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates ??', async () => {
|
||||
const scope = {
|
||||
get() { return undefined; },
|
||||
};
|
||||
expect(evaluate(await first('null ?? 1')))
|
||||
.to.deep.include({value: 1});
|
||||
expect(evaluate(await first('undefined ?? 1'), {scope}))
|
||||
.to.deep.include({value: 1});
|
||||
expect(evaluate(await first('2 ?? 1')))
|
||||
.to.deep.include({value: 2});
|
||||
});
|
||||
|
||||
test('evaluates instanceof', async () => {
|
||||
const scope = {
|
||||
get() { return Object; },
|
||||
};
|
||||
expect(evaluate(await first('({}) instanceof Object'), {scope}))
|
||||
.to.deep.include({value: true});
|
||||
});
|
||||
|
||||
test('evaluates promised expressions', async () => {
|
||||
const evaluated = evaluate(await first('(await 1) + (await 2)'));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(3);
|
||||
});
|
53
app/swcx/evaluators/call.js
Normal file
53
app/swcx/evaluators/call.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import fastCall from '@/util/fast-call.js';
|
||||
import {
|
||||
isComputed,
|
||||
isMemberExpression,
|
||||
unwrap,
|
||||
} from '@/swcx/types.js';
|
||||
|
||||
export default function(node, {evaluate, scope}) {
|
||||
let asyncArgs = false;
|
||||
const args = [];
|
||||
for (let i = 0; i < node.arguments.length; i++) {
|
||||
const {expression: arg} = node.arguments[i];
|
||||
const {async, value} = evaluate(arg, {scope});
|
||||
asyncArgs ||= async;
|
||||
args.push(value);
|
||||
}
|
||||
const {callee: wrappedCallee} = node;
|
||||
const callee = unwrap(wrappedCallee);
|
||||
const callOptional = callee.wrapper?.optional || node.wrapper?.optional;
|
||||
const invoke = (fn, holder, args) => {
|
||||
if (callOptional && !fn) {
|
||||
return undefined;
|
||||
}
|
||||
return fastCall(fn, holder, args);
|
||||
};
|
||||
if (!isMemberExpression(callee)) {
|
||||
const {async, value} = evaluate(callee, {scope});
|
||||
if (asyncArgs || async) {
|
||||
return {
|
||||
async: true,
|
||||
value: Promise
|
||||
.all([value, Promise.all(args)])
|
||||
.then(([callee, args]) => invoke(callee, undefined, args)),
|
||||
};
|
||||
}
|
||||
return {value: invoke(value, undefined, args)};
|
||||
}
|
||||
const {
|
||||
object,
|
||||
property,
|
||||
} = callee;
|
||||
const O = evaluate(object, {scope});
|
||||
const P = isComputed(property) ? evaluate(property, {scope}) : {value: property.value};
|
||||
if (asyncArgs || O.async || P.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: Promise
|
||||
.all([O.value, P.value, Promise.all(args)])
|
||||
.then(([O, P, args]) => invoke(callOptional ? O?.[P] : O[P], O, args)),
|
||||
};
|
||||
}
|
||||
return {value: invoke(callOptional ? O.value?.[P.value] : O.value[P.value], O.value, args)};
|
||||
}
|
64
app/swcx/evaluators/call.test.js
Normal file
64
app/swcx/evaluators/call.test.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
const scopeTest = test.extend({
|
||||
scope: async ({}, use) => {
|
||||
await use({
|
||||
S: {O: {}},
|
||||
get(k) { return this.S[k]; },
|
||||
set(k, v) { return this.S[k] = v; }
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
scopeTest('evaluates calls', async ({scope}) => {
|
||||
scope.set('f', (...args) => args.reduce((l, r) => l + r, 0));
|
||||
const evaluated = evaluate(await first('f(1, 2, 3)'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(6);
|
||||
});
|
||||
|
||||
scopeTest('evaluates async calls', async ({scope}) => {
|
||||
const f = (...args) => args.reduce((l, r) => l + r, 0);
|
||||
scope.set('f', f);
|
||||
scope.set('O', {f});
|
||||
const evaluated = evaluate(await first('f(await 1, 2, 3)'), {scope});
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(6);
|
||||
const evaluatedOptional = evaluate(await first('O?.f(await 1, 2, 3)'), {scope});
|
||||
expect(evaluatedOptional.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluatedOptional.value)
|
||||
.to.equal(6);
|
||||
});
|
||||
|
||||
scopeTest('evaluates member calls', async ({scope}) => {
|
||||
scope.set('O', {f: (...args) => args.reduce((l, r) => l + r, 0)});
|
||||
expect(evaluate(await first('O.f(1, 2, 3)'), {scope}).value)
|
||||
.to.equal(6);
|
||||
expect(evaluate(await first('O["f"](1, 2, 3)'), {scope}).value)
|
||||
.to.equal(6);
|
||||
});
|
||||
|
||||
scopeTest('evaluates optional calls', async ({scope}) => {
|
||||
scope.set('O', {});
|
||||
expect(evaluate(await first('g?.(1, 2, 3)'), {scope}).value)
|
||||
.to.equal(undefined);
|
||||
expect(evaluate(await first('O?.g(1, 2, 3)'), {scope}).value)
|
||||
.to.equal(undefined);
|
||||
expect(evaluate(await first('O?.g?.(1, 2, 3)'), {scope}).value)
|
||||
.to.equal(undefined);
|
||||
});
|
||||
|
||||
scopeTest('evaluates async calls', async ({scope}) => {
|
||||
scope.set('O', {f: (...args) => args.reduce((l, r) => l + r, 0)});
|
||||
const evaluated = evaluate(await first('O.f(await 1, 2, 3)'), {scope});
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(6);
|
||||
});
|
13
app/swcx/evaluators/conditional.js
Normal file
13
app/swcx/evaluators/conditional.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default function(node, {evaluate, scope}) {
|
||||
const test = evaluate(node.test, {scope});
|
||||
if (test.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: (
|
||||
Promise.resolve(test.value)
|
||||
.then((test) => evaluate(test ? node.consequent : node.alternate).value, {scope})
|
||||
),
|
||||
};
|
||||
}
|
||||
return evaluate(test.value ? node.consequent : node.alternate, {scope});
|
||||
}
|
43
app/swcx/evaluators/conditional.test.js
Normal file
43
app/swcx/evaluators/conditional.test.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
const scopeTest = test.extend({
|
||||
scope: async ({}, use) => {
|
||||
await use({
|
||||
S: {O: {}},
|
||||
get(k) { return this.S[k]; },
|
||||
set(k, v) { return this.S[k] = v; }
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
scopeTest('evaluates conditional expression', async ({scope}) => {
|
||||
scope.set('x', true);
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first('x ? 2 : 3'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(2);
|
||||
scope.set('x', false);
|
||||
evaluated = evaluate(await first('x ? 2 : 3'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(3);
|
||||
});
|
||||
|
||||
scopeTest('evaluates async conditional expression', async ({scope}) => {
|
||||
scope.set('x', true);
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first('(await x) ? 2 : 3'), {scope});
|
||||
expect(await evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(2);
|
||||
scope.set('x', false);
|
||||
evaluated = evaluate(await first('(await x) ? 2 : 3'), {scope});
|
||||
expect(await evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(3);
|
||||
});
|
||||
|
3
app/swcx/evaluators/identifier.js
Normal file
3
app/swcx/evaluators/identifier.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function(node, {scope}) {
|
||||
return {value: scope.get(node.value)};
|
||||
}
|
3
app/swcx/evaluators/literal.js
Normal file
3
app/swcx/evaluators/literal.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function({value}) {
|
||||
return {value};
|
||||
}
|
14
app/swcx/evaluators/literal.test.js
Normal file
14
app/swcx/evaluators/literal.test.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
test('evaluates numeric literals', async () => {
|
||||
expect(evaluate(await first('1')))
|
||||
.to.deep.include({value: 1});
|
||||
});
|
||||
|
||||
test('evaluates string literals', async () => {
|
||||
expect(evaluate(await first('"1"')))
|
||||
.to.deep.include({value: '1'});
|
||||
});
|
16
app/swcx/evaluators/member.js
Normal file
16
app/swcx/evaluators/member.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {
|
||||
isComputed,
|
||||
} from '@/swcx/types.js';
|
||||
|
||||
export default function({object, property, wrapper}, {evaluate, scope}) {
|
||||
const member = (O, P) => (wrapper?.optional ? O?.[P] : O[P]);
|
||||
const O = evaluate(object, {scope});
|
||||
const P = isComputed(property) ? evaluate(property, {scope}) : {value: property.value};
|
||||
if (O.async || P.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: Promise.all([O.value, P.value]).then(([O, P]) => member(O, P)),
|
||||
};
|
||||
}
|
||||
return {value: member(O.value, P.value)};
|
||||
}
|
44
app/swcx/evaluators/member.test.js
Normal file
44
app/swcx/evaluators/member.test.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
const scopeTest = test.extend({
|
||||
scope: async ({}, use) => {
|
||||
await use({
|
||||
S: {O: {x: 32}},
|
||||
get(k) { return this.S[k]; },
|
||||
set(k, v) { return this.S[k] = v; }
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
scopeTest('evaluates member expression', async ({scope}) => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first('O.x'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(32);
|
||||
});
|
||||
|
||||
scopeTest('evaluates optional member expression', async ({scope}) => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first('O?.y'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(undefined);
|
||||
});
|
||||
|
||||
scopeTest('evaluates computed member expression', async ({scope}) => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first('O["x"]'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(32);
|
||||
});
|
||||
|
||||
scopeTest('evaluates async member expression', async ({scope}) => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first('O[await "x"]'), {scope});
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(32);
|
||||
});
|
88
app/swcx/evaluators/object.js
Normal file
88
app/swcx/evaluators/object.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
import {
|
||||
isComputed,
|
||||
isIdentifier,
|
||||
isKeyValueProperty,
|
||||
isNumericLiteral,
|
||||
isSpreadElement,
|
||||
isStringLiteral,
|
||||
} from '@/swcx/types.js';
|
||||
|
||||
export default function(node, {evaluate, scope}) {
|
||||
const {properties} = node;
|
||||
let isAsync = false;
|
||||
const entries = [];
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (isKeyValueProperty(properties[i])) {
|
||||
const {key, value} = properties[i];
|
||||
let k;
|
||||
if (isComputed(key)) {
|
||||
k = evaluate(key, {scope});
|
||||
}
|
||||
else if (isIdentifier(key)) {
|
||||
k = {value: key.value};
|
||||
}
|
||||
else if (isNumericLiteral(key)) {
|
||||
k = {value: key.value};
|
||||
}
|
||||
else if (isStringLiteral(key)) {
|
||||
k = {value: key.value};
|
||||
}
|
||||
/* v8 ignore next 3 */
|
||||
else {
|
||||
throw new Error(`property key type ${key.type} not implemented`);
|
||||
}
|
||||
const v = evaluate(value, {scope});
|
||||
isAsync ||= k.async || v.async;
|
||||
if (k.async || v.async) {
|
||||
entries.push(Promise.all([k.value, v.value]));
|
||||
}
|
||||
else {
|
||||
entries.push([k.value, v.value]);
|
||||
}
|
||||
}
|
||||
if (isSpreadElement(properties[i])) {
|
||||
const {arguments: argument} = properties[i];
|
||||
const spreading = evaluate(argument, {scope});
|
||||
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((entries) => {
|
||||
const flat = [];
|
||||
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++) {
|
||||
flat.push(entry[j]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
flat.push(entry);
|
||||
}
|
||||
}
|
||||
return Object.fromEntries(flat);
|
||||
})
|
||||
: Object.fromEntries(entries),
|
||||
};
|
||||
}
|
60
app/swcx/evaluators/object.test.js
Normal file
60
app/swcx/evaluators/object.test.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
test('evaluates object expression', async () => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first(`({
|
||||
["foo"]: 16,
|
||||
bar: 32,
|
||||
'baz': 64,
|
||||
})`));
|
||||
expect(evaluated.value)
|
||||
.to.deep.equal({
|
||||
foo: 16,
|
||||
bar: 32,
|
||||
baz: 64,
|
||||
});
|
||||
});
|
||||
|
||||
test('evaluates async object expression', async () => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first(`({
|
||||
foo: await 32,
|
||||
})`));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.deep.equal({
|
||||
foo: 32,
|
||||
});
|
||||
});
|
||||
|
||||
test('evaluates object spread expression', async () => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first(`({
|
||||
foo: 16,
|
||||
...({bar: 32}),
|
||||
})`));
|
||||
expect(evaluated.value)
|
||||
.to.deep.equal({
|
||||
foo: 16,
|
||||
bar: 32,
|
||||
});
|
||||
});
|
||||
|
||||
test('evaluates async spread expression', async () => {
|
||||
let evaluated;
|
||||
evaluated = evaluate(await first(`({
|
||||
foo: 16,
|
||||
...(await {bar: 32}),
|
||||
})`));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.deep.equal({
|
||||
foo: 16,
|
||||
bar: 32,
|
||||
});
|
||||
});
|
24
app/swcx/evaluators/unary.js
Normal file
24
app/swcx/evaluators/unary.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
export default function(node, {evaluate, scope}) {
|
||||
const unary = (arg) => {
|
||||
switch (node.operator) {
|
||||
case '+' : return +arg;
|
||||
case '-' : return -arg;
|
||||
case '!' : return !arg;
|
||||
case '~' : return ~arg;
|
||||
case 'typeof': return typeof arg;
|
||||
case 'void' : return undefined;
|
||||
// case 'delete': ...
|
||||
/* v8 ignore next 2 */
|
||||
default:
|
||||
throw new Error(`operator not implemented: ${node.operator}`);
|
||||
}
|
||||
};
|
||||
const arg = evaluate(node.argument, {scope});
|
||||
if (arg.async) {
|
||||
return {
|
||||
async: true,
|
||||
value: Promise.resolve(arg.value).then(unary),
|
||||
};
|
||||
}
|
||||
return {value: unary(arg.value)};
|
||||
}
|
42
app/swcx/evaluators/unary.test.js
Normal file
42
app/swcx/evaluators/unary.test.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
test('evaluates +', async () => {
|
||||
expect(evaluate(await first('+1')))
|
||||
.to.deep.include({value: 1});
|
||||
});
|
||||
|
||||
test('evaluates -', async () => {
|
||||
expect(evaluate(await first('-1')))
|
||||
.to.deep.include({value: -1});
|
||||
});
|
||||
|
||||
test('evaluates !', async () => {
|
||||
expect(evaluate(await first('!true')))
|
||||
.to.deep.include({value: false});
|
||||
});
|
||||
|
||||
test('evaluates ~', async () => {
|
||||
expect(evaluate(await first('~1')))
|
||||
.to.deep.include({value: -2});
|
||||
});
|
||||
|
||||
test('evaluates typeof', async () => {
|
||||
expect(evaluate(await first('typeof "a"')))
|
||||
.to.deep.include({value: 'string'});
|
||||
});
|
||||
|
||||
test('evaluates void', async () => {
|
||||
expect(evaluate(await first('void 0')))
|
||||
.to.deep.include({value: undefined});
|
||||
});
|
||||
|
||||
test('evaluates promised unary expression', async () => {
|
||||
const evaluated = evaluate(await first('-(await 4)'));
|
||||
expect(evaluated.async)
|
||||
.to.equal(true);
|
||||
expect(await evaluated.value)
|
||||
.to.equal(-4);
|
||||
});
|
23
app/swcx/evaluators/update.js
Normal file
23
app/swcx/evaluators/update.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export default function(node, {evaluate, scope}) {
|
||||
const {argument, operator, prefix} = node;
|
||||
const {value} = evaluate(argument, {scope});
|
||||
const update = (value) => {
|
||||
if (prefix) {
|
||||
switch (operator) {
|
||||
case '++': return scope.set(argument.value, value + 1);
|
||||
case '--': return scope.set(argument.value, value - 1);
|
||||
}
|
||||
}
|
||||
switch (operator) {
|
||||
case '++':
|
||||
scope.set(argument.value, value + 1);
|
||||
return value;
|
||||
case '--':
|
||||
scope.set(argument.value, value - 1);
|
||||
return value;
|
||||
}
|
||||
/* v8 ignore next */
|
||||
throw new Error(`operator not implemented: ${operator}`);
|
||||
};
|
||||
return {value: update(value)};
|
||||
}
|
50
app/swcx/evaluators/update.test.js
Normal file
50
app/swcx/evaluators/update.test.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {first} from '@/swcx/builders.js';
|
||||
import evaluate from '@/swcx/evaluate.js';
|
||||
|
||||
const scopeTest = test.extend({
|
||||
scope: async ({}, use) => {
|
||||
await use({
|
||||
S: {O: {}},
|
||||
get(k) { return this.S[k]; },
|
||||
set(k, v) { return this.S[k] = v; }
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
scopeTest('evaluates postfix updates', async ({scope}) => {
|
||||
scope.set('x', 4);
|
||||
let evaluated = evaluate(await first('y = x++'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(4);
|
||||
expect(scope.get('x'))
|
||||
.to.equal(5);
|
||||
expect(scope.get('y'))
|
||||
.to.equal(4);
|
||||
evaluated = evaluate(await first('y = x--'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(5);
|
||||
expect(scope.get('x'))
|
||||
.to.equal(4);
|
||||
expect(scope.get('y'))
|
||||
.to.equal(5);
|
||||
});
|
||||
|
||||
scopeTest('evaluates prefix updates', async ({scope}) => {
|
||||
scope.set('x', 4);
|
||||
let evaluated = evaluate(await first('y = ++x'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(5);
|
||||
expect(scope.get('x'))
|
||||
.to.equal(5);
|
||||
expect(scope.get('y'))
|
||||
.to.equal(5);
|
||||
evaluated = evaluate(await first('y = --x'), {scope});
|
||||
expect(evaluated.value)
|
||||
.to.equal(4);
|
||||
expect(scope.get('x'))
|
||||
.to.equal(4);
|
||||
expect(scope.get('y'))
|
||||
.to.equal(4);
|
||||
});
|
266
app/swcx/sandbox.js
Normal file
266
app/swcx/sandbox.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
import evaluate from '@/swcx/evaluate.js';
|
||||
import Scope from '@/swcx/scope.js';
|
||||
import traverse, {TRAVERSAL_PATH} from '@/swcx/traverse.js';
|
||||
import {
|
||||
isArrayPattern,
|
||||
isBlockStatement,
|
||||
isDoWhileStatement,
|
||||
isExpressionStatement,
|
||||
isForStatement,
|
||||
isIdentifier,
|
||||
isIfStatement,
|
||||
isObjectPattern,
|
||||
isVariableDeclarator,
|
||||
isWhileStatement,
|
||||
} from '@/swcx/types.js';
|
||||
|
||||
export default class Sandbox {
|
||||
|
||||
ast;
|
||||
generator;
|
||||
scopes;
|
||||
|
||||
constructor(ast, context = {}) {
|
||||
this.ast = ast;
|
||||
this.$$context = context;
|
||||
this.compile();
|
||||
}
|
||||
|
||||
compile() {
|
||||
let scope = new Scope();
|
||||
scope.context = this.$$context;
|
||||
this.scopes = new WeakMap([[this.ast, scope]]);
|
||||
traverse(
|
||||
this.ast,
|
||||
(node, verb) => {
|
||||
if (
|
||||
isBlockStatement(node)
|
||||
|| isForStatement(node)
|
||||
) {
|
||||
switch (verb) {
|
||||
case 'enter': {
|
||||
scope = new Scope(scope);
|
||||
break;
|
||||
}
|
||||
case 'exit': {
|
||||
scope = scope.parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ('enter' === verb) {
|
||||
this.scopes.set(node, scope);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
get context() {
|
||||
return this.scopes.get(this.ast).context;
|
||||
}
|
||||
|
||||
destructureArray(id, init) {
|
||||
const scope = this.scopes.get(id);
|
||||
const {elements} = id;
|
||||
for (let i = 0; i < elements.length; ++i) {
|
||||
const element = elements[i];
|
||||
if (null === element) {
|
||||
continue;
|
||||
}
|
||||
if (isIdentifier(element)) {
|
||||
scope.allocate(element.value, init[i]);
|
||||
}
|
||||
/* v8 ignore next 3 */
|
||||
else {
|
||||
throw new Error(`destructureArray(): Can't array destructure type ${element.type}`);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
destructureObject(id, init) {
|
||||
const scope = this.scopes.get(id);
|
||||
const {properties} = id;
|
||||
for (let i = 0; i < properties.length; ++i) {
|
||||
const property = properties[i];
|
||||
if (isObjectPattern(property.value)) {
|
||||
this.destructureObject(property.value, init[property.key.value], scope);
|
||||
}
|
||||
else {
|
||||
scope.allocate(
|
||||
property.value ? property.value.value : property.key.value,
|
||||
init[property.key.value],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evaluate(node) {
|
||||
return evaluate(
|
||||
node,
|
||||
{scope: this.scopes.get(node)},
|
||||
);
|
||||
}
|
||||
|
||||
*execute(node, parent) {
|
||||
let keys = TRAVERSAL_PATH[node.type];
|
||||
if (isVariableDeclarator(node)) {
|
||||
const {id} = node;
|
||||
const scope = this.scopes.get(node);
|
||||
if (null === node.init) {
|
||||
scope.allocate(id.value, undefined);
|
||||
}
|
||||
else {
|
||||
const init = this.evaluate(node.init);
|
||||
if (isIdentifier(id)) {
|
||||
if (init.async) {
|
||||
yield {
|
||||
async: true,
|
||||
value: Promise.resolve(init.value).then((value) => {
|
||||
scope.allocate(id.value, value);
|
||||
}),
|
||||
};
|
||||
}
|
||||
else {
|
||||
scope.allocate(id.value, init.value);
|
||||
}
|
||||
}
|
||||
else if (isArrayPattern(id)) {
|
||||
const promiseOrVoid = init.async
|
||||
? Promise.resolve(init.value).then((init) => this.destructureArray(id, init))
|
||||
: this.destructureArray(id, init.value);
|
||||
if (promiseOrVoid) {
|
||||
yield {
|
||||
async: true,
|
||||
value: promiseOrVoid,
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (isObjectPattern(id)) {
|
||||
const promiseOrVoid = init.async
|
||||
? Promise.resolve(init.value).then((init) => this.destructureObject(id, init))
|
||||
: this.destructureObject(id, init.value);
|
||||
if (promiseOrVoid) {
|
||||
yield {
|
||||
async: true,
|
||||
value: promiseOrVoid,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Blocks...
|
||||
if (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(branch),
|
||||
};
|
||||
}
|
||||
else {
|
||||
branch(value);
|
||||
}
|
||||
}
|
||||
// Loops...
|
||||
let loop = false;
|
||||
if (isForStatement(node)) {
|
||||
const {value} = this.execute(node.init, node).next();
|
||||
if (value?.async) {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
do {
|
||||
if (
|
||||
isForStatement(node)
|
||||
|| isWhileStatement(node)
|
||||
) {
|
||||
const {async, value} = this.evaluate(node.test);
|
||||
if (async) {
|
||||
yield {
|
||||
async: true,
|
||||
value: Promise.resolve(value).then((value) => {
|
||||
keys = value ? ['body'] : [];
|
||||
}),
|
||||
};
|
||||
}
|
||||
else {
|
||||
keys = value ? ['body'] : [];
|
||||
}
|
||||
loop = keys.length > 0;
|
||||
}
|
||||
// Recur...
|
||||
let children = [];
|
||||
if (keys instanceof Function) {
|
||||
children = keys(node);
|
||||
}
|
||||
else {
|
||||
for (const key of keys) {
|
||||
children.push(...(Array.isArray(node[key]) ? node[key] : [node[key]]));
|
||||
}
|
||||
}
|
||||
for (const child of children) {
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
yield* this.execute(child, node);
|
||||
}
|
||||
// Loops...
|
||||
if (isForStatement(node)) {
|
||||
const result = this.execute(node.update, node).next();
|
||||
if (result.value?.async) {
|
||||
yield result.value;
|
||||
}
|
||||
}
|
||||
if (isDoWhileStatement(node)) {
|
||||
const test = this.evaluate(node.test);
|
||||
if (test.async) {
|
||||
yield {
|
||||
async: true,
|
||||
value: Promise.resolve(test.value).then((value) => {
|
||||
loop = value;
|
||||
}),
|
||||
};
|
||||
}
|
||||
else {
|
||||
loop = test.value;
|
||||
}
|
||||
}
|
||||
} while (loop);
|
||||
if (isExpressionStatement(node)) {
|
||||
yield this.evaluate(node.expression);
|
||||
}
|
||||
// yield ForStatement afterthought.
|
||||
if (isForStatement(parent) && !isBlockStatement(node)) {
|
||||
yield this.evaluate(node);
|
||||
/* v8 ignore next */
|
||||
}
|
||||
}
|
||||
|
||||
run(ops = 1000) {
|
||||
let result;
|
||||
for (let i = 0; i < ops; ++i) {
|
||||
result = this.step();
|
||||
if (result.done || result.value?.async) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
step() {
|
||||
if (!this.generator) {
|
||||
this.generator = this.execute(this.ast);
|
||||
}
|
||||
const result = this.generator.next();
|
||||
if (result.done) {
|
||||
this.generator = undefined;
|
||||
this.compile();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
197
app/swcx/sandbox.test.js
Normal file
197
app/swcx/sandbox.test.js
Normal file
|
@ -0,0 +1,197 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {parse} from '@swc/core';
|
||||
import Sandbox from '@/swcx/sandbox.js';
|
||||
|
||||
test('declares variables', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
const scalar = 1;
|
||||
const asyncScalar = await 2;
|
||||
const array = [3, 4, 5];
|
||||
const asyncArray = await [6, 7, 8];
|
||||
const object = {9: '10'};
|
||||
const asyncObject = await {11: '12'};
|
||||
`),
|
||||
);
|
||||
let result;
|
||||
do {
|
||||
result = sandbox.step();
|
||||
if (result.value?.async) {
|
||||
await result.value.async;
|
||||
}
|
||||
} while (!result.done);
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({
|
||||
scalar: 1,
|
||||
asyncScalar: 2,
|
||||
array: [3, 4, 5],
|
||||
asyncArray: [6, 7, 8],
|
||||
object: {9: '10'},
|
||||
asyncObject: {11: '12'},
|
||||
});
|
||||
});
|
||||
|
||||
test('destructures variables', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
const [a, , c] = [1, 2, 3];
|
||||
const {x: x1, y, z: {zz}} = {x: 4, y: 5, z: {zz: 6}};
|
||||
const [d, e] = await [7, 8];
|
||||
const {t, u: {uu}} = {t: 9, u: {uu: await 10}};
|
||||
`),
|
||||
);
|
||||
let result;
|
||||
do {
|
||||
result = sandbox.step();
|
||||
if (result.value?.async) {
|
||||
await result.value.value;
|
||||
}
|
||||
} while (!result.done);
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({
|
||||
a: 1,
|
||||
c: 3,
|
||||
x1: 4,
|
||||
y: 5,
|
||||
zz: 6,
|
||||
d: 7,
|
||||
e: 8,
|
||||
t: 9,
|
||||
uu: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test('runs arbitrary number of ops', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
const foo = [];
|
||||
for (let i = 0; i < 1500; ++i) {
|
||||
foo.push(i);
|
||||
}
|
||||
`),
|
||||
);
|
||||
sandbox.run(1000);
|
||||
expect(sandbox.context.foo.length)
|
||||
.to.equal(1000);
|
||||
sandbox.run(1000);
|
||||
expect(sandbox.context.foo.length)
|
||||
.to.equal(1500);
|
||||
expect(true)
|
||||
.to.be.true;
|
||||
});
|
||||
|
||||
test('evaluates conditional branches', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
let foo, bar;
|
||||
if (true) {
|
||||
foo = 1;
|
||||
}
|
||||
else {
|
||||
foo = 2;
|
||||
}
|
||||
if (await false) {
|
||||
bar = 1;
|
||||
}
|
||||
else {
|
||||
bar = 2;
|
||||
}
|
||||
`),
|
||||
);
|
||||
let result;
|
||||
do {
|
||||
result = sandbox.step();
|
||||
if (result.value?.async) {
|
||||
await result.value.value;
|
||||
}
|
||||
} while (!result.done);
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test('evaluates loops', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
let x = 0, y = 0, a = 0, b = 0, c = 0;
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
x += 1;
|
||||
}
|
||||
for (let i = await 0; i < await 3; i = 1 + await i) {
|
||||
y += 1;
|
||||
}
|
||||
do {
|
||||
a += 1;
|
||||
} while (a < 3);
|
||||
do {
|
||||
b += 1;
|
||||
} while (await b < 3);
|
||||
while (c < 3) {
|
||||
c += 1;
|
||||
}
|
||||
`),
|
||||
);
|
||||
let result;
|
||||
do {
|
||||
result = sandbox.step();
|
||||
if (result.value?.async) {
|
||||
await result.value.value;
|
||||
}
|
||||
} while (!result.done);
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({
|
||||
a: 3,
|
||||
b: 3,
|
||||
c: 3,
|
||||
x: 3,
|
||||
y: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test('retuns undefined for nonexistent variables in scope', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
const x = y
|
||||
`),
|
||||
);
|
||||
sandbox.run();
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({
|
||||
x: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('sets variables in global scope', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
x = y
|
||||
`),
|
||||
);
|
||||
sandbox.run();
|
||||
expect(sandbox.context)
|
||||
.to.deep.equal({
|
||||
x: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('runs arbitrary number of ops', async () => {
|
||||
const sandbox = new Sandbox(
|
||||
await parse(`
|
||||
const foo = [];
|
||||
for (let i = 0; i < 1500; ++i) {
|
||||
foo.push(i);
|
||||
}
|
||||
`),
|
||||
);
|
||||
sandbox.run(1000);
|
||||
expect(sandbox.context.foo.length)
|
||||
.to.equal(1000);
|
||||
sandbox.run(1000);
|
||||
expect(sandbox.context.foo.length)
|
||||
.to.equal(1500);
|
||||
expect(true)
|
||||
.to.be.true;
|
||||
});
|
40
app/swcx/scope.js
Normal file
40
app/swcx/scope.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
export default class Scope {
|
||||
|
||||
context = {};
|
||||
parent = null;
|
||||
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
allocate(key, value) {
|
||||
this.context[key] = value;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
let walk = this;
|
||||
while (walk) {
|
||||
if (key in walk.context) {
|
||||
return walk.context[key];
|
||||
}
|
||||
walk = walk.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
let walk = this;
|
||||
while (walk) {
|
||||
if (key in walk.context) {
|
||||
walk.context[key] = value;
|
||||
return value;
|
||||
}
|
||||
if (!walk.parent) {
|
||||
walk.context[key] = value;
|
||||
return value;
|
||||
}
|
||||
walk = walk.parent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
63
app/swcx/traverse.js
Normal file
63
app/swcx/traverse.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
export const TRAVERSAL_PATH = {
|
||||
ArrayExpression: (node) => node.elements.map(({expression}) => expression),
|
||||
ArrayPattern: ['elements'],
|
||||
AssignmentExpression: ['left', 'right'],
|
||||
AssignmentPatternProperty: ['key'],
|
||||
AwaitExpression: ['argument'],
|
||||
BinaryExpression: ['left', 'right'],
|
||||
BlockStatement: ['stmts'],
|
||||
BooleanLiteral: [],
|
||||
CallExpression: (node) => ([
|
||||
node.callee,
|
||||
...node.arguments.map(({expression}) => expression),
|
||||
]),
|
||||
Computed: ['expression'],
|
||||
ConditionalExpression: ['alternate', 'consequent', 'test'],
|
||||
DoWhileStatement: ['body', 'test'],
|
||||
ExpressionStatement: ['expression'],
|
||||
ForStatement: ['body', 'init', 'test', 'update'],
|
||||
Identifier: [],
|
||||
IfStatement: ['alternate', 'consequent', 'test'],
|
||||
KeyValuePatternProperty: ['key', 'value'],
|
||||
KeyValueProperty: ['key', 'value'],
|
||||
MemberExpression: ['object', 'property'],
|
||||
Module: ['body'],
|
||||
NullLiteral: [],
|
||||
NumericLiteral: [],
|
||||
ObjectExpression: ['properties'],
|
||||
ObjectPattern: ['properties'],
|
||||
OptionalChainingExpression: ['base'],
|
||||
ParenthesisExpression: ['expression'],
|
||||
RegExpLiteral: [],
|
||||
StringLiteral: [],
|
||||
UnaryExpression: ['argument'],
|
||||
UpdateExpression: ['argument'],
|
||||
VariableDeclaration: ['declarations'],
|
||||
VariableDeclarator: ['id', 'init'],
|
||||
WhileStatement: ['body', 'test'],
|
||||
};
|
||||
|
||||
export default function traverse(node, visitor) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!(node.type in TRAVERSAL_PATH)) {
|
||||
throw new Error(`node type ${node.type} not traversable`);
|
||||
}
|
||||
visitor(node, 'enter');
|
||||
const path = TRAVERSAL_PATH[node.type];
|
||||
let children;
|
||||
if (path instanceof Function) {
|
||||
children = path(node);
|
||||
}
|
||||
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) {
|
||||
if (child) {
|
||||
traverse(child, visitor);
|
||||
}
|
||||
}
|
||||
visitor(node, 'exit');
|
||||
}
|
143
app/swcx/types.js
Normal file
143
app/swcx/types.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
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 isComputed(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'Computed') {
|
||||
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 isKeyValueProperty(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'KeyValueProperty') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isMemberExpression(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'MemberExpression') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isNumericLiteral(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'NumericLiteral') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isObjectPattern(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'ObjectPattern') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isSpreadElement(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'SpreadElement') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isStringLiteral(node) {
|
||||
/* v8 ignore next 3 */
|
||||
if (!node || node.type !== 'StringLiteral') {
|
||||
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;
|
||||
}
|
||||
|
||||
export function unwrap(node) {
|
||||
let wrapped = node;
|
||||
switch (node.type) {
|
||||
case 'Computed':
|
||||
wrapped = unwrap(node.expression);
|
||||
break;
|
||||
case 'OptionalChainingExpression':
|
||||
wrapped = unwrap(node.base);
|
||||
break;
|
||||
case 'ParenthesisExpression':
|
||||
wrapped = unwrap(node.expression);
|
||||
break;
|
||||
}
|
||||
if (node !== wrapped) {
|
||||
wrapped.wrapper = node;
|
||||
}
|
||||
return wrapped;
|
||||
}
|
36
app/util/fast-call.js
Normal file
36
app/util/fast-call.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
export default function(fn, holder, args) {
|
||||
if (holder) {
|
||||
const {name} = fn;
|
||||
if (name in holder && holder[name] === fn) {
|
||||
switch (args.length) {
|
||||
case 0 : return holder[name]();
|
||||
case 1 : return holder[name](args[0]);
|
||||
case 2 : return holder[name](args[0], args[1]);
|
||||
case 3 : return holder[name](args[0], args[1], args[2]);
|
||||
case 4 : return holder[name](args[0], args[1], args[2], args[3]);
|
||||
case 5 : return holder[name](args[0], args[1], args[2], args[3], args[4]);
|
||||
default: return holder[name](...args);
|
||||
}
|
||||
}
|
||||
const bound = fn.bind(holder);
|
||||
switch (args.length) {
|
||||
case 0 : return bound();
|
||||
case 1 : return bound(args[0]);
|
||||
case 2 : return bound(args[0], args[1]);
|
||||
case 3 : return bound(args[0], args[1], args[2]);
|
||||
case 4 : return bound(args[0], args[1], args[2], args[3]);
|
||||
case 5 : return bound(args[0], args[1], args[2], args[3], args[4]);
|
||||
default: return bound(...args);
|
||||
}
|
||||
}
|
||||
switch (args.length) {
|
||||
case 0 : return fn();
|
||||
case 1 : return fn(args[0]);
|
||||
case 2 : return fn(args[0], args[1]);
|
||||
case 3 : return fn(args[0], args[1], args[2]);
|
||||
case 4 : return fn(args[0], args[1], args[2], args[3]);
|
||||
case 5 : return fn(args[0], args[1], args[2], args[3], args[4]);
|
||||
default: return fn(...args);
|
||||
}
|
||||
}
|
||||
|
201
package-lock.json
generated
201
package-lock.json
generated
|
@ -13,6 +13,7 @@
|
|||
"@remix-run/express": "^2.9.2",
|
||||
"@remix-run/node": "^2.9.2",
|
||||
"@remix-run/react": "^2.9.2",
|
||||
"@swc/core": "^1.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.18.2",
|
||||
"idb-keyval": "^6.2.1",
|
||||
|
@ -6316,6 +6317,206 @@
|
|||
"url": "https://opencollective.com/storybook"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-Wynbo79uIVBgmq3TPcTMdtXUkqk69IPSVuzo7/Jl1OhR4msC7cUaoRB1216ZanWttrAZ4/g6u17w9XZG4fzp1A==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.6.0",
|
||||
"@swc/core-darwin-x64": "1.6.0",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.6.0",
|
||||
"@swc/core-linux-arm64-gnu": "1.6.0",
|
||||
"@swc/core-linux-arm64-musl": "1.6.0",
|
||||
"@swc/core-linux-x64-gnu": "1.6.0",
|
||||
"@swc/core-linux-x64-musl": "1.6.0",
|
||||
"@swc/core-win32-arm64-msvc": "1.6.0",
|
||||
"@swc/core-win32-ia32-msvc": "1.6.0",
|
||||
"@swc/core-win32-x64-msvc": "1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.0.tgz",
|
||||
"integrity": "sha512-W1Mwk0WRrJ5lAVkYRPxpxOmwu8p9ASXeOmiORhXvE7DYREyI30005xlqSOITU1pfSNKj7G9u3+9DjsOzPPPbBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.6.0.tgz",
|
||||
"integrity": "sha512-EzxLnpPC1zgLb2Y0iVUG6b+/GUv43k6uJUIs52UzxOnBElYP/WeItI3RJ+LUMFzCpZMk/IxB10wofEoeQ1H/Xg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.0.tgz",
|
||||
"integrity": "sha512-uP/STDjWZ5N6lc8mxJFsex4NXDaqhfzd8UOrI3LfdV97+4faE4/BC6bVqDNHFFzZi0PHuVBxD6md7IfPjugk6A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.0.tgz",
|
||||
"integrity": "sha512-UgNz6anowcnYzJtZohzpii31FOgouBHJqluiq+p2geX/agbC+KfOKwVXdljn95+Qc4ygBuw/hjKjgF2msOLeVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.0.tgz",
|
||||
"integrity": "sha512-xPV6qrnj4nFwXQbIv70C1Kn5z5Th53sirIY76aEonr78qeC6+ywaBZR4uLFNHsljVjyuvVQfTTcl2qraGhu6oQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.0.tgz",
|
||||
"integrity": "sha512-xTeWn4OT5uQ+DxT2cy94ngK8tF1U/5fMC49/V6FhCS2Wh+Xa/O+OWcOyKvYtk3b0eGYS4iNIRKgzog7fLSFtvQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.0.tgz",
|
||||
"integrity": "sha512-3P01mYD5XbyaVLT0MGZmZE+ZdgmGSvuvIhSejRDBlEXqkFnH79nWds+KsE+91hzVU8XsgzX57Yzv4eO5dlIuPw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.0.tgz",
|
||||
"integrity": "sha512-xFuook1efU0ctzMAEeol4eI7J6+k/c/pMJpp/NP/4JJDnhlHwAi2iyiZcID8YZS+ePHgXMLndGdIMHVv/wIPkQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.0.tgz",
|
||||
"integrity": "sha512-VCJa5vTywxzASqvf9OEUM5SZBcNrWbuIkSGM5T9guuBzyrh/tSqVHjzOWL9qpP69uPVj5G/I5bJObLiUKErhvQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.0.tgz",
|
||||
"integrity": "sha512-L7i8WBSIJTQiMONJGHnznDydZmlJIqHjZ3VhBHeTTms8cEAuwkAVgzPwgr5cD9GhmcwdeBI9iYdOuKr1pUx19Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.8.tgz",
|
||||
"integrity": "sha512-RNFA3+7OJFNYY78x0FYwi1Ow+iF1eF5WvmfY1nXPOEH4R2p/D4Cr1vzje7dNAI2aLFqpv8Wyz4oKSWqIZArpQA==",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "9.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"@remix-run/express": "^2.9.2",
|
||||
"@remix-run/node": "^2.9.2",
|
||||
"@remix-run/react": "^2.9.2",
|
||||
"@swc/core": "^1.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.18.2",
|
||||
"idb-keyval": "^6.2.1",
|
||||
|
|
Loading…
Reference in New Issue
Block a user