expression-language
Version:
Javascript implementation of symfony/expression-language
230 lines (228 loc) • 14 kB
JavaScript
"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());
}
});