UNPKG

rd-parse-jsexpr

Version:

ES6 expression grammar for use with rd-parse parser generator

221 lines (177 loc) 7.3 kB
import test from 'node:test'; import assert from 'node:assert/strict'; import snapshot from 'usnap'; snapshot.setup(import.meta.url); import Parser from 'rd-parse'; import Grammar from '../src/grammar.js'; const parser = Parser(Grammar); test('empty input', () => { assert.throws(() => parser(''), new Error('Unexpected token at 1:1. Remainder: ')); }); test('faulty input', () => { assert.throws(() => parser('a + \n]'), new Error('Unexpected token at 2:1. Remainder: ]')); }); test('Identifier', () => { const ast = parser(' bla '); assert.deepEqual(ast, { type: 'Identifier', name: 'bla' }); assert.equal(ast.pos, 1); }); test('boolean literals', async t => { const ast = parser('false || true'); await snapshot(ast, t.name); }); test('a string literal', () => { const ast = parser(' "bla \\" bla" '); assert.deepEqual(ast, { type: 'Literal', value: 'bla " bla', raw: '"bla \\" bla"' }); }); test('a number literal', () => { const ast = parser(' 5e2 '); assert.deepEqual(ast, { type: 'Literal', value: 500, raw: '5e2' }); }); test('operator precedence', async t => { const ast = parser('x*x + y*y'); await snapshot(ast, t.name); }); test('left associativity', async t => { const ast = parser('a + b - c'); await snapshot(ast, t.name); }); test('exponentiation operator (right associativity)', async t => { const ast = parser('a ** b ** c / 2'); await snapshot(ast, t.name); }); test('ArrowFunction, simple', () => { const ast = parser('x => x * x'); assert.equal(ast.type, 'ArrowFunction'); assert.deepEqual(ast.parameters, { bindingType: 'SingleName', name: 'x' }); assert.equal(ast.parameters.rest, undefined); assert.equal(ast.result.type, 'BinaryExpression'); }); test('ArrowFunction, with initializer and rest parameter', () => { const ast = parser('(c = 1, ...a) => c + a.length'); assert.equal(ast.type, 'ArrowFunction'); assert.equal(ast.parameters.bound.length, 1); assert.equal(ast.parameters.bindingType, 'FormalParameters'); assert.deepEqual(ast.parameters.bound[0].pattern, { bindingType: 'SingleName', name: 'c' }); assert.deepEqual(ast.parameters.bound[0].initializer, { type: 'Literal', value: 1, raw: '1' }); assert.deepEqual(ast.parameters.rest, { bindingType: 'SingleName', name: 'a' }); }); test('template literals', () => { const ast = parser('`${a} + ${b} is ${a + b}`'); assert.equal(ast.type, 'TemplateLiteral'); assert.deepEqual(ast.parts[0], ['expressions', { type: 'Identifier', name: 'a' }]); assert.deepEqual(ast.parts[1], ['chunks', ' + ']); assert.deepEqual(ast.parts[2], ['expressions', { type: 'Identifier', name: 'b' }]); assert.deepEqual(ast.parts[3], ['chunks', ' is ']); assert.equal(ast.parts[4][1].type, 'BinaryExpression'); }); test('template litarals 2', async t => { const input = '`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`'; await snapshot(parser(input), t.name); }); test('object literal short notation', async t => { const input = '{ foo }'; const ast = parser(input); await snapshot(ast, t.name); assert.equal(ast.properties[0].name, 'foo'); assert.equal(ast.properties[0].value.pos, 2); assert.equal(ast.properties[0].value.text, 'foo'); }); test('pos and text for member and call expressions', () => { const input = 'obj.method(a, b)'; const ast = parser(' ' + input + ' '); assert.equal(ast.type, 'CallExpression'); assert.equal(ast.pos, 1); assert.equal(ast.text, input); assert.equal(ast.callee.type, 'MemberExpression'); assert.equal(ast.callee.pos, 1); assert.equal(ast.callee.text, 'obj.method'); assert.equal(ast.callee.object.type, 'Identifier'); assert.equal(ast.callee.object.pos, 1); assert.equal(ast.callee.object.text, 'obj'); }); test('pos and text for unary expressions', () => { const input = 'typeof ~foo'; const ast = parser(input); assert.equal(ast.type, 'UnaryExpression'); assert.equal(ast.operator, 'typeof'); assert.equal(ast.argument.type, 'UnaryExpression'); assert.equal(ast.argument.operator, '~'); assert.equal(ast.argument.pos, 7); assert.equal(ast.argument.text, '~foo'); }); test('pos and text for binary expressions', () => { const input = 'a + 2 * b'; const ast = parser(input); assert.equal(ast.type, 'BinaryExpression'); assert.equal(ast.operator, '+'); assert.equal(ast.text, input); assert.equal(ast.right.type, 'BinaryExpression'); assert.equal(ast.right.operator, '*'); assert.equal(ast.right.pos, 4); assert.equal(ast.right.text, '2 * b'); }); test('pos and text for bound names', () => { const input = '(a, ...r) => a + r[0]'; const ast = parser(input); assert.equal(ast.type, 'ArrowFunction'); assert.equal(ast.parameters.bound[0].pattern.bindingType, 'SingleName'); assert.equal(ast.parameters.bound[0].pattern.pos, 1); assert.equal(ast.parameters.rest.bindingType, 'SingleName'); assert.equal(ast.parameters.rest.pos, 7); }); test('new expression + memeber expression', () => { const input = 'new Array(3).length'; const ast = parser(input); assert.equal(ast.type, 'MemberExpression'); assert.equal(ast.object.type, 'NewExpression'); assert.equal(ast.object.ctor.type, 'Identifier'); assert.equal(ast.object.ctor.pos, 4); }); test('new expression + call expression', () => { const input = 'new Array(3).map((_, i) => i)'; const ast = parser(input); assert.equal(ast.type, 'CallExpression'); assert.equal(ast.callee.type, 'MemberExpression'); assert.equal(ast.callee.object.type, 'NewExpression'); assert.equal(ast.callee.object.ctor.type, 'Identifier'); assert.equal(ast.callee.object.ctor.pos, 4); }); test('pos and text for arrow functions', () => { const input = ' () => x '; const ast = parser(input); assert.equal(ast.type, 'ArrowFunction'); assert.equal(ast.pos, 1); assert.equal(ast.text, '() => x'); }); test('object literal with spread', async t => { const input = '{ foo: 5, ...bar }'; const ast = parser(input); await snapshot(ast, t.name); assert.equal(ast.type, 'ObjectLiteral'); assert.equal(ast.properties[0].name, 'foo'); assert.equal(ast.properties[1].spread.type, 'Identifier'); assert.equal(ast.properties[1].spread.name, 'bar'); }); test('array literals', async t => { const input = '[[,,,], [,5,,], [...a,, 5, ...b]]'; const ast = parser(input); await snapshot(ast, t.name); }); test('destructuring', async t => { const input = '([{y: {z1 = 5, ...z2} = {x:6}}, z3, ...z4]) => (z1 * z2.x) * z3 * z4.length'; const ast = parser(input); await snapshot(ast, t.name); }); test('pos and text for bound short-hand parameters', () => { const input = '({ x, y = 5 }) => x * y' const ast = parser(input); assert.equal(ast.type, 'ArrowFunction'); assert.equal(ast.parameters.bound[0].pattern.bindingType, 'ObjectPattern'); assert.equal(ast.parameters.bound[0].pattern.bound[0].prop, 'x'); assert.equal(ast.parameters.bound[0].pattern.bound[0].pattern.bindingType, 'SingleName'); assert.equal(ast.parameters.bound[0].pattern.bound[0].pattern.name, 'x'); assert.equal(ast.parameters.bound[0].pattern.bound[0].pattern.text, 'x'); assert.equal(ast.parameters.bound[0].pattern.bound[0].pattern.pos, 3); assert.equal(ast.parameters.bound[0].pattern.bound[1].pattern.text, 'y'); });