UNPKG

expression-language

Version:

Javascript implementation of symfony/expression-language

230 lines (228 loc) 14 kB
"use strict"; var _Lexer = require("../Lexer"); var _Parser = _interopRequireWildcard(require("../Parser")); var _ArgumentsNode = _interopRequireDefault(require("../Node/ArgumentsNode")); var _ConstantNode = _interopRequireDefault(require("../Node/ConstantNode")); var _NameNode = _interopRequireDefault(require("../Node/NameNode")); var _UnaryNode = _interopRequireDefault(require("../Node/UnaryNode")); var _BinaryNode = _interopRequireDefault(require("../Node/BinaryNode")); var _GetAttrNode = _interopRequireDefault(require("../Node/GetAttrNode")); var _ConditionalNode = _interopRequireDefault(require("../Node/ConditionalNode")); var _NullCoalesceNode = _interopRequireDefault(require("../Node/NullCoalesceNode")); var _ArrayNode = _interopRequireDefault(require("../Node/ArrayNode")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function getParseData() { let args = new _ArgumentsNode.default(); args.addElement(new _ConstantNode.default('arg1')); args.addElement(new _ConstantNode.default(2)); args.addElement(new _ConstantNode.default(true)); let arrayNode = new _ArrayNode.default(); arrayNode.addElement(new _NameNode.default('bar')); return [[new _NameNode.default('a'), 'a', ['a']], [new _ConstantNode.default('a'), '"a"'], [new _ConstantNode.default(3), '3'], [new _ConstantNode.default(false), 'false'], [new _ConstantNode.default(true), 'true'], [new _ConstantNode.default(null), 'null'], [new _UnaryNode.default('-', new _ConstantNode.default(3)), '-3'], [new _BinaryNode.default('-', new _ConstantNode.default(3), new _ConstantNode.default(3)), '3 - 3'], [new _BinaryNode.default('*', new _BinaryNode.default('-', new _ConstantNode.default(3), new _ConstantNode.default(3)), new _ConstantNode.default(2)), '(3 - 3) * 2'], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true), new _ArgumentsNode.default(), _GetAttrNode.default.PROPERTY_CALL), 'foo.bar', ['foo']], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true), new _ArgumentsNode.default(), _GetAttrNode.default.METHOD_CALL), 'foo.bar()', ['foo']], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('not', true), new _ArgumentsNode.default(), _GetAttrNode.default.METHOD_CALL), 'foo.not()', ['foo']], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true), args, _GetAttrNode.default.METHOD_CALL), 'foo.bar("arg1", 2, true)', ['foo']], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default(3), new _ArgumentsNode.default(), _GetAttrNode.default.ARRAY_CALL), 'foo[3]', ['foo']], [new _ConditionalNode.default(new _ConstantNode.default(true), new _ConstantNode.default(true), new _ConstantNode.default(false)), 'true ? true ? false'], [new _BinaryNode.default('matches', new _ConstantNode.default('foo'), new _ConstantNode.default('/foo/')), '"foo" matches "/foo/"'], [new _BinaryNode.default('contains', new _ConstantNode.default('foo'), new _ConstantNode.default('fo')), '"foo" contains "fo"'], [new _BinaryNode.default('starts with', new _ConstantNode.default('foo'), new _ConstantNode.default('fo')), '"foo" starts with "fo"'], [new _BinaryNode.default('ends with', new _ConstantNode.default('foo'), new _ConstantNode.default('oo')), '"foo" ends with "oo"'], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true, true), new _ArgumentsNode.default(), _GetAttrNode.default.PROPERTY_CALL), "foo?.bar", ['foo']], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true, true), new _ArgumentsNode.default(), _GetAttrNode.default.METHOD_CALL), "foo?.bar()", ['foo']], [new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('not', true, true), new _ArgumentsNode.default(), _GetAttrNode.default.METHOD_CALL), "foo?.not()", ['foo']], [new _NullCoalesceNode.default(new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true), new _ArgumentsNode.default(), _GetAttrNode.default.PROPERTY_CALL), new _ConstantNode.default('default')), 'foo.bar ?? "default"', ['foo']], [new _NullCoalesceNode.default(new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('bar', true), new _ArgumentsNode.default(), _GetAttrNode.default.ARRAY_CALL), new _ConstantNode.default('default')), 'foo["bar"] ?? "default"', ['foo']], // chained calls [createGetAttrNode(createGetAttrNode(createGetAttrNode(createGetAttrNode(new _NameNode.default('foo'), 'bar', _GetAttrNode.default.METHOD_CALL), 'foo', _GetAttrNode.default.METHOD_CALL), 'baz', _GetAttrNode.default.PROPERTY_CALL), '3', _GetAttrNode.default.ARRAY_CALL), 'foo.bar().foo().baz[3]', ['foo']], [new _NameNode.default('foo'), 'bar', [{ foo: 'bar' }]], // Operators collisions [new _BinaryNode.default('in', new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('not', true), new _ArgumentsNode.default(), _GetAttrNode.default.PROPERTY_CALL), arrayNode), 'foo.not in [bar]', ['foo', 'bar']], [new _BinaryNode.default('or', new _UnaryNode.default('not', new _NameNode.default('foo')), new _GetAttrNode.default(new _NameNode.default('foo'), new _ConstantNode.default('not', true), new _ArgumentsNode.default(), _GetAttrNode.default.PROPERTY_CALL)), 'not foo or foo.not', ['foo']], [new _BinaryNode.default('xor', new _NameNode.default('foo'), new _NameNode.default('bar')), 'foo xor bar', ['foo', 'bar']], [new _BinaryNode.default('..', new _ConstantNode.default(0), new _ConstantNode.default(3)), '0..3'], [new _BinaryNode.default('+', new _ConstantNode.default(0), new _ConstantNode.default(0.1)), '0+.1']]; } function getLintData() { // Keys are kept as descriptive strings; values are objects with expression, names, optional checks and exception return { 'valid expression': { expression: 'foo["some_key"].callFunction(a ? b)', names: ['foo', 'a', 'b'] }, 'valid expression with null safety': { expression: 'foo["some_key"]?.callFunction(a ? b)', names: ['foo', 'a', 'b'] }, 'allow expression with unknown names': { expression: 'foo.bar', names: [], checks: _Parser.IGNORE_UNKNOWN_VARIABLES }, 'allow expression with unknown functions': { expression: 'foo()', names: [], checks: _Parser.IGNORE_UNKNOWN_FUNCTIONS }, 'allow expression with unknown functions and names': { expression: 'foo(bar)', names: [], checks: _Parser.IGNORE_UNKNOWN_FUNCTIONS | _Parser.IGNORE_UNKNOWN_VARIABLES }, 'array with trailing comma': { expression: '[value1, value2, value3,]', names: ['value1', 'value2', 'value3'] }, 'hashmap with trailing comma': { expression: '{val1: value1, val2: value2, val3: value3,}', names: ['value1', 'value2', 'value3'] }, 'disallow expression with unknown names by default': { expression: 'foo.bar', names: [], checks: 0, exception: 'Variable "foo" is not valid around position 1 for expression `foo.bar' }, 'disallow expression with unknown functions by default': { expression: 'foo()', names: [], checks: 0, exception: 'The function "foo" does not exist around position 1 for expression `foo()' }, 'operator collisions': { expression: 'foo.not in [bar]', names: ['foo', 'bar'] }, 'incorrect expression ending': { expression: 'foo["a"] foo["b"]', names: ['foo'], checks: 0, exception: 'Unexpected token "name" of value "foo" around position 10 for expression `foo["a"] foo["b"]`.' }, 'incorrect operator': { expression: 'foo["some_key"] // 2', names: ['foo'], checks: 0, exception: 'Unexpected token "operator" of value "/" around position 18 for expression `foo["some_key"] // 2`.' }, 'incorrect array': { expression: '[value1, value2 value3]', names: ['value1', 'value2', 'value3'], checks: 0, exception: 'An array element must be followed by a comma. Unexpected token "name" of value "value3" ("punctuation" expected with value ",") around position 17 for expression `[value1, value2 value3]`.' }, 'incorrect array element': { expression: 'foo["some_key")', names: ['foo'], checks: 0, exception: 'Unclosed "[" around position 3 for expression `foo["some_key")`.' }, 'incorrect hash key': { expression: '{+: value1}', names: ['value1'], checks: 0, exception: 'A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "operator" of value "+" around position 2 for expression `{+: value1}`.' }, 'missed array key': { expression: 'foo[]', names: ['foo'], checks: 0, exception: 'Unexpected token "punctuation" of value "]" around position 5 for expression `foo[]`.' }, 'missed closing bracket in sub expression': { expression: 'foo[(bar ? bar : "default"]', names: ['foo', 'bar'], checks: 0, exception: 'Unclosed "(" around position 4 for expression `foo[(bar ? bar : "default"]`.' }, 'incorrect hash following': { expression: '{key: foo key2: bar}', names: ['foo', 'bar'], checks: 0, exception: 'A hash value must be followed by a comma. Unexpected token "name" of value "key2" ("punctuation" expected with value ",") around position 11 for expression `{key: foo key2: bar}`.' }, 'incorrect hash assign': { expression: '{key => foo}', names: ['foo'], checks: 0, exception: 'Unexpected character "=" around position 5 for expression `{key => foo}`.' }, 'incorrect array as hash using': { expression: '[foo: foo]', names: ['foo'], checks: 0, exception: 'An array element must be followed by a comma. Unexpected token "punctuation" of value ":" ("punctuation" expected with value ",") around position 5 for expression `[foo: foo]`.' } }; } function createGetAttrNode(node, item, type) { return new _GetAttrNode.default(node, new _ConstantNode.default(item, _GetAttrNode.default.ARRAY_CALL !== type), new _ArgumentsNode.default(), type); } function getInvalidPostfixData() { return [['foo."#"', ['foo']], ['foo."bar"', ['foo']], ['foo.**', ['foo']], ['foo.123', ['foo']]]; } test("parse with invalid name", () => { try { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)("foo")); console.log("The parser should throw an error."); expect(true).toBe(false); // This should fail } catch (err) { expect(err.toString()).toContain('Variable "foo" is not valid around position 1'); } }); test("parse with zero in names", () => { try { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)("foo"), [0]); console.log("The parser should throw an error."); expect(true).toBe(false); // This should fail } catch (err) { expect(err.toString()).toContain('Variable "foo" is not valid around position 1'); } }); test("parse primary expression with unknown function throws", () => { try { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)("foo()")); console.log("The parser should throw an error."); expect(true).toBe(false); } catch (err) { expect(err.toString()).toContain('The function "foo" does not exist around position 1'); } }); test('parse with invalid postfix data', () => { let invalidPostfixData = getInvalidPostfixData(); for (let oneTest of invalidPostfixData) { try { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)(oneTest[0]), oneTest[1]); console.log("The parser should throw an error."); expect(true).toBe(false); // This should fail } catch (err) { expect(err.name).toBe('SyntaxError'); } } }); test('name proposal', () => { try { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)('foo > bar'), ['foo', 'baz']); console.log("The parser should throw an error."); expect(true).toBe(false); // This should fail } catch (err) { expect(err.toString()).toContain('Did you mean "baz"?'); } }); test('lint', () => { let lintData = getLintData(); for (let testKey in lintData) { let testData = lintData[testKey]; if (testData.exception) { try { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)(testData.expression), testData.names, testData.checks ?? 0); console.log("The parser should throw an error."); expect(true).toBe(false); } catch (err) { expect(err.toString()).toContain(testData.exception); } } else { let parser = new _Parser.default(); parser.parse((0, _Lexer.tokenize)(testData.expression), testData.names, testData.checks ?? 0); } } }); test('parse', () => { let parseData = getParseData(); for (let parseDatum of parseData) { //console.log("Testing ", parseDatum[1], parseDatum[2]); let parser = new _Parser.default(); let generated = parser.parse((0, _Lexer.tokenize)(parseDatum[1]), parseDatum[2]); expect(generated.toString()).toBe(parseDatum[0].toString()); } });