/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
extensions/amp-bind/0.1/test/test-bind-expression.js
880 строк
30 KB
Kristofer Baxter
♻️ Remove Notice from JS and Markdown (#35717)
19 авг 2021, 21:21
Не верифицирован
19 авг 2021, 21:21
f564b7e
Код
Авторство
О чём код?
import {BindExpression} from '../bind-expression'; import {BindMacro} from '../bind-macro'; describes.sandboxed('BindExpression', {}, () => { const argumentTypeError = 'Unexpected argument type'; const unsupportedFunctionError = 'not a supported function'; const expressionSizeExceededError = 'exceeds max'; /** * @param {string} expression * @param {Object=} opt_scope * @return {*} */ function evaluate(expression, opt_scope) { return new BindExpression(expression, {}).evaluate(opt_scope || {}); } describe('operations', () => { it('arithmetic', () => { expect(evaluate('-1')).to.equal(-1); expect(evaluate('1 + 2')).to.equal(3); expect(evaluate('2 - 3.5')).to.equal(-1.5); expect(evaluate('3 * 4')).to.equal(12); expect(evaluate('4 / 5')).to.equal(0.8); expect(evaluate('5 % 4')).to.equal(1); expect(evaluate('1 / 0')).to.equal(Infinity); expect(evaluate('0 / 0')).to.be.NaN; }); it('comparison', () => { expect(evaluate('2 > 1')).to.be.true; expect(evaluate('1 > 1')).to.be.false; expect(evaluate('1 >= 1')).to.be.true; expect(evaluate('1 >= 2')).to.be.false; expect(evaluate('1 < 2')).to.be.true; expect(evaluate('0 < 0')).to.be.false; expect(evaluate('1 <= 1')).to.be.true; expect(evaluate('1 <= 0')).to.be.false; expect(evaluate('0 == 1')).to.be.false; expect(evaluate('1 == 1')).to.be.true; expect(evaluate('0 != 1')).to.be.true; expect(evaluate('1 != 1')).to.be.false; }); it('logical', () => { expect(evaluate('!false')).to.be.true; expect(evaluate('true && true')).to.be.true; expect(evaluate('true && false')).to.be.false; expect(evaluate('false && false')).to.be.false; expect(evaluate('true || true')).to.be.true; expect(evaluate('true || false')).to.be.true; expect(evaluate('false || false')).to.be.false; }); it('ternary', () => { expect(evaluate('true ? "a" : "b"')).to.be.equal('a'); expect(evaluate('false ? "a" : "b"')).to.be.equal('b'); }); it('disallow: operators with side effects', () => { expect(() => { evaluate('foo = 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo += 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo -= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo *= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo /= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo %= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo **= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo <<= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo >>= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo >>>= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo &= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo ^= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo |= 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo++', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo--', {foo: 0}); }).to.throw(); expect(() => { evaluate('~foo', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo << 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo >> 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('foo >>> 1', {foo: 0}); }).to.throw(); expect(() => { evaluate('new Object()', {foo: 0}); }).to.throw(); expect(() => { evaluate('delete foo', {foo: 0}); }).to.throw(); }); /** @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators */ it('disallow: non-allowlisted operators', () => { expect(evaluate('this')).to.be.null; expect(evaluate('self')).to.be.null; expect(evaluate('global')).to.be.null; expect(evaluate('function')).to.be.null; expect(evaluate('class')).to.be.null; expect(evaluate('yield')).to.be.null; expect(evaluate('await')).to.be.null; expect(evaluate('new')).to.be.null; expect(evaluate('super')).to.be.null; expect(() => { evaluate('function*'); }).to.throw(); expect(() => { evaluate('/ab+c/i'); }).to.throw(); expect(() => { evaluate('yield*'); }).to.throw(); expect(() => { evaluate('async function*'); }).to.throw(); }); }); describe('operator precedence', () => { it('arithmetic operators', () => { expect(evaluate('-1 + 2')).to.equal(1); expect(evaluate('1 - -0.5')).to.equal(1.5); expect(evaluate('1 + -2 * 3')).to.equal(-5); expect(evaluate('1 / 2 - 3')).to.equal(-2.5); expect(evaluate('4 % 3 - 2 * 1')).to.equal(-1); }); it('comparison operators', () => { expect(evaluate('true == 2 > 1')).to.equal(true); expect(evaluate('true == 2 >= 1')).to.equal(true); expect(evaluate('true == 2 < 1')).to.equal(false); expect(evaluate('true == 2 <= 1')).to.equal(false); expect(evaluate('1 > 2 == true')).to.equal(false); expect(evaluate('1 >= 2 == true')).to.equal(false); expect(evaluate('1 < 2 == true')).to.equal(true); expect(evaluate('1 <= 2 == true')).to.equal(true); }); it('logical operators', () => { expect(evaluate('!false && true')).to.be.true; expect(evaluate('false || !true')).to.be.false; expect(evaluate('true && false || true')).to.be.true; expect(evaluate('true && false == false')).to.be.true; expect(evaluate('false || false == true')).to.be.false; expect(evaluate('false == !true')).to.be.true; }); }); describe('strings', () => { it('basic support', () => { expect(evaluate('"a"')).to.equal('a'); expect(evaluate('"a".length')).to.equal(1); expect(evaluate('"a" + "b"')).to.equal('ab'); expect(evaluate('"a" + 1')).to.equal('a1'); expect(evaluate('+"1"')).to.equal(1); }); it('should parse special characters', () => { expect(evaluate('"\\n"')).to.equal('\n'); expect(evaluate('"\\t"')).to.equal('\t'); expect(evaluate('"\\u041f"')).to.equal('\u041f'); // Single quote strings should be handled. expect(evaluate("'\\n'")).to.equal('\n'); expect(evaluate("'\\t'")).to.equal('\t'); expect(evaluate("'\\u041f'")).to.equal('\u041f'); // Escaping special chars should work. expect(evaluate('"\\\\n"')).to.equal('\\n'); expect(evaluate('"\\\\t"')).to.equal('\\t'); expect(evaluate('"\\\\u041f"')).to.equal('\\u041f'); // Double quotes inside the string should be escaped. expect(evaluate('\'a"b"c\'')).to.equal('a"b"c'); expect(evaluate('\'a"\\n"c\'')).to.equal('a"\n"c'); }); it('allowlisted functions', () => { expect(evaluate('"abc".charAt(0)')).to.equal('a'); expect(evaluate('"abc".charCodeAt(0)')).to.equal(97); expect(evaluate('"abc".concat("def")')).to.equal('abcdef'); expect(evaluate('"abc".indexOf("b")')).to.equal(1); expect(evaluate('"aaa".lastIndexOf("a")')).to.equal(2); expect(evaluate('"abc".slice(0, 2)')).to.equal('ab'); expect(evaluate('"abc".replace("bc", "xy")')).to.equal('axy'); expect(evaluate('"a-b-c".split("-")')).to.deep.equal(['a', 'b', 'c']); expect(evaluate('"abc".substr(1)')).to.equal('bc'); expect(evaluate('"abc".substring(0, 2)')).to.equal('ab'); expect(evaluate('"ABC".toLowerCase()')).to.equal('abc'); expect(evaluate('"abc".toUpperCase()')).to.equal('ABC'); }); it('escaped quotes', () => { expect(evaluate(`'\\"'`)).to.equal(`"`); expect(evaluate(`"\\'"`)).to.equal(`'`); expect(evaluate(`"Hello \\"World\\""`)).to.equal(`Hello "World"`); expect(evaluate(`'Hello\\'s world'`)).to.equal(`Hello's world`); expect(evaluate(`"\t\r"`)).to.equal(`\t\r`); }); it('ban: non-allowlisted string methods', () => { expect(() => { evaluate('"abc".anchor()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('"abc".includes("ab")'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('"abc".link()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('"abc".repeat(2)'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('"abc".search()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('" abc ".trim()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('"abc ".trimRight()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('" abc".trimLeft()'); }).to.throw(Error, unsupportedFunctionError); }); }); describe('numbers', () => { it('prototype functions', () => { expect(evaluate('3.14.toExponential()')).to.equal('3.14e+0'); expect(evaluate('3.14.toFixed()')).to.equal('3'); expect(evaluate('3.14.toPrecision(2)')).to.equal('3.1'); expect(evaluate('3.14.toString()')).to.equal('3.14'); }); }); describe('variables', () => { it('basic support', () => { expect(evaluate('foo', {foo: 'bar'})).to.equal('bar'); expect(evaluate('foo', {foo: 1})).to.equal(1); expect(evaluate('foo', {foo: [1, 2, 3]})).to.deep.equal([1, 2, 3]); expect(evaluate('foo', {foo: {'bar': 'qux'}})).to.deep.equal({ bar: 'qux', }); expect(evaluate('{"foo": bar}', {bar: 'qux'})).to.deep.equal({ foo: 'qux', }); expect(evaluate('[foo]', {foo: 'bar'})).to.deep.equal(['bar']); expect(evaluate('foo[1]', {foo: ['b', 'c']})).to.equal('c'); expect(evaluate('foo.length', {foo: [1, 2, 3]})).to.equal(3); expect(evaluate('"abc".charAt(foo)', {foo: 1})).to.equal('b'); }); }); describe('arrays', () => { it('literals', () => { expect(evaluate('[]')).to.deep.equal([]); expect(evaluate('["a", "b"].length')).to.equal(2); expect(evaluate('[1, "a", [], {}]')).to.deep.equal([1, 'a', [], {}]); expect(evaluate('["a", "b"][1]')).to.equal('b'); expect(evaluate('["a", foo][1]', {foo: 'b'})).to.equal('b'); }); it('trailing commas in literals', () => { expect(evaluate('[1,2,3,]')).to.deep.equal([1, 2, 3]); expect(evaluate('["a", "b",].length')).to.equal(2); expect(evaluate('[1, "a", [], {},]')).to.deep.equal([1, 'a', [], {}]); expect(evaluate('["a", "b",][1]')).to.equal('b'); expect(evaluate('["a", foo,][1]', {foo: 'b'})).to.equal('b'); }); it('invalid array access', () => { expect(evaluate('["a", "b"][-1]')).to.be.null; expect(evaluate('["a", "b"][2]')).to.be.null; expect(evaluate('["a", "b"][0.5]')).to.be.null; expect(evaluate('["a", "b"]["a"]')).to.be.null; expect(evaluate('["a", []][[]]')).to.be.null; expect(evaluate('["a", {}][{}]')).to.be.null; }); it('prototype functions', () => { expect(evaluate('["a", "b"].concat(["c", "d"])')).to.deep.equal([ 'a', 'b', 'c', 'd', ]); expect(evaluate('["a", "a"].indexOf("a")')).to.equal(0); expect(evaluate('["a", "b", "c"].join("-")')).to.equal('a-b-c'); expect(evaluate('["a", "a"].lastIndexOf("a")')).to.equal(1); expect(evaluate('["a", "b", "c"].slice(1, 2)')).to.deep.equal(['b']); expect(evaluate('[1, 2, 3, 4, 5].includes(3)')).to.be.true; }); it('custom Array#sort()', () => { expect(evaluate('[11, 1, 2].sort()')).to.deep.equal([1, 11, 2]); expect(evaluate('[11, 1, 2].sort((x, y) => x - y)')).to.deep.equal([ 1, 2, 11, ]); const a = [11, 1, 2]; expect(evaluate('a.sort()', {a})).to.deep.equal([1, 11, 2]); expect(evaluate('a.sort((x, y) => x - y)', {a})).to.deep.equal([ 1, 2, 11, ]); // Sort should be out-of-place i.e. does not sort the caller. expect(evaluate('a.sort().concat(a)', {a})).to.deep.equal([ 1, 11, 2, 11, 1, 2, ]); }); it('custom Array#splice()', () => { expect(evaluate('[1, 2, 3].splice()')).to.deep.equal([1, 2, 3]); expect(evaluate('[1, 2, 3].splice(1)')).to.deep.equal([1]); expect(evaluate('[1, 2, 3].splice(1, 1)')).to.deep.equal([1, 3]); expect(evaluate('[1, 2, 3].splice(1, 1, 47)')).to.deep.equal([1, 47, 3]); const a = [1, 2, 3]; expect(evaluate('a.splice()', {a})).to.deep.equal([1, 2, 3]); expect(evaluate('a.splice(1)', {a})).to.deep.equal([1]); expect(evaluate('a.splice(1, 1)', {a})).to.deep.equal([1, 3]); expect(evaluate('a.splice(1, 1, 47)', {a})).to.deep.equal([1, 47, 3]); // Splice should be out-of-place i.e. does not splice the caller. expect(evaluate('a.splice(1).concat(a)', {a})).to.deep.equal([ 1, 1, 2, 3, ]); }); it('non-allowlisted functions', () => { expect(() => { evaluate('["a", "b", "c"].find()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('["a", "b", "c"].forEach()'); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.find()', {foo: ['a', 'b', 'c']}); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.forEach()', {foo: ['a', 'b', 'c']}); }).to.throw(Error, unsupportedFunctionError); }); }); describe('objects', () => { it('literals', () => { expect(evaluate('{}')).to.deep.equal({}); expect(evaluate('{}["a"]')).to.be.null; expect(evaluate('{}[{}]')).to.be.null; expect(evaluate('{a: "b"}')).to.deep.equal({a: 'b'}); expect(evaluate('{"a": "b"}')).to.deep.equal({'a': 'b'}); expect(evaluate('{123: "b"}')).to.deep.equal({123: 'b'}); expect(evaluate('{true: "b"}')).to.deep.equal({true: 'b'}); expect(evaluate('{null: "b"}')).to.deep.equal({null: 'b'}); // Unquoted string keys should _not_ be evaluated as expressions. expect(evaluate('{a: "b"}', {a: 'foo'})).to.deep.equal({a: 'b'}); expect(() => evaluate('{1+1: "b"}')).to.throw(); }); it('computed property names', () => { expect(evaluate('{["a" + "b" + "c"]: 123}')).to.deep.equal({abc: 123}); expect(evaluate('{[foo]: 123}', {foo: 'abc'})).to.deep.equal({abc: 123}); }); it('trailing commas in object literals', () => { expect(evaluate('{a: "b",}')).to.deep.equal({a: 'b'}); expect(evaluate('{a: "b", c: "d",}')).to.deep.equal({a: 'b', c: 'd'}); }); it('undefined vars and properties should be null', () => { expect(evaluate('foo')).to.be.null; expect(evaluate('foo.bar')).to.be.null; expect(evaluate('foo["bar"]')).to.be.null; expect(evaluate('foo[bar]')).to.be.null; expect(evaluate('foo[0]')).to.be.null; }); it('static functions', () => { expect(evaluate('keys({a: 1, b: 2})')).to.deep.equal(['a', 'b']); expect(evaluate('values({a: 1, b: 2})')).to.deep.equal([1, 2]); }); }); describe('function invocation', () => { it('Math functions', () => { expect(evaluate('abs(-1)')).to.equal(1); expect(evaluate('ceil(0.1)')).to.equal(1); expect(evaluate('floor(1.9)')).to.equal(1); expect(evaluate('max(0, 1)')).to.equal(1); expect(evaluate('min(0, 1)')).to.equal(0); expect(evaluate('round(0.6)')).to.equal(1); expect(evaluate('pow(2, 2)')).to.equal(4); expect(evaluate('sqrt(4)')).to.equal(2); expect(evaluate('log(20.2)')).to.equal(3.005682604407159); const r = evaluate('random()'); expect(r).to.be.at.least(0); expect(r).to.be.at.below(1); expect(evaluate('sign(-1)')).to.equal(-1); // Functions should not conflict with scope variables. expect(evaluate('abs(-2) + abs', {abs: 2})).to.equal(4); expect(evaluate('sqrt(4) + sqrt', {sqrt: 2})).to.equal(4); expect(evaluate('log(20) + log', {log: 1})).to.equal(3.995732273553991); // Don't support non-allowlisted functions. expect(() => { evaluate('sin(0.5)'); }).to.throw(unsupportedFunctionError); expect(() => { evaluate('trunc(13.37)'); }).to.throw(unsupportedFunctionError); // Don't support calling functions with `Math.` prefix. expect(() => { evaluate('Math.abs(-1)', {Math}); }).to.throw(unsupportedFunctionError); expect(() => { evaluate('Math.sqrt(4)', {Math}); }).to.throw(unsupportedFunctionError); expect(() => { evaluate('Math.log(20.2)', {Math}); }).to.throw(unsupportedFunctionError); }); it('encodeURI and encodeURIComponent', () => { expect( evaluate('encodeURI("http://google.com/s p a c e.html")') ).to.equal('http://google.com/s%20p%20a%20c%20e.html'); expect( evaluate('encodeURIComponent("http://google.com/foo?foo=bar")') ).to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Ffoo%3Dbar'); }); it('splice()', () => { const a = [1, 2, 3]; expect(() => evaluate('splice()')).to.throw(/not an array/); expect(() => evaluate('splice(x)', {x: 8472})).to.throw(/not an array/); expect(evaluate('splice(a)', {a})).to.not.equal(a); expect(evaluate('splice(a)', {a})).to.deep.equal(a); expect(evaluate('splice(a, 1)', {a})).to.deep.equal([1]); expect(evaluate('splice(a, 1, 1)', {a})).to.deep.equal([1, 3]); expect(evaluate('splice(a, 1, 1, 47)', {a})).to.deep.equal([1, 47, 3]); }); it('sort()', () => { const a = [2, 3, 1]; expect(() => evaluate('sort()')).to.throw(/not an array/); expect(() => evaluate('sort("abc")')).to.throw(/not an array/); expect(evaluate('sort(a)', {a})).to.not.equal(a); expect(evaluate('sort(a)', {a})).to.deep.equal([1, 2, 3]); }); it('return null when caller is null', () => { expect(evaluate('null.charAt(0)')).to.be.null; expect(evaluate('null.includes(3)')).to.be.null; expect(evaluate('a.indexOf("baz")', {a: null})).to.be.null; expect(evaluate('(false || null).replace("foo", "bar")')).to.be.null; }); it('disallow: function declarations', () => { expect(() => { evaluate('(function() {})'); }).to.throw(); expect(() => { evaluate('function foo() {}'); }).to.throw(); expect(() => { evaluate('new Function()'); }).to.throw(); expect(() => { evaluate('Function()'); }).to.throw(); expect(() => { evaluate('() => {}'); }).to.throw(); expect(() => { evaluate('class Foo {}'); }).to.throw(); }); it('disallow: invocation of custom functions in scope', () => { const scope = { foo: { bar: () => { 'bar'; }, }, baz: () => { 'baz'; }, qux: window.Function, }; // baz() throws a parse error because functions must have a caller. expect(() => { evaluate('baz()', scope); }).to.throw(); expect(() => { evaluate('foo.bar()', scope); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.qux("a", "return a")', scope); }).to.throw(unsupportedFunctionError); }); it('disallow: invocation of prototype functions', () => { const scope = { foo: '', bar: [], }; expect(() => { evaluate('foo.constructor()', scope); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.__defineGetter__()', scope); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.__defineSetter__()', scope); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('bar.constructor()', scope); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('bar.__defineGetter__()', scope); }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('bar.__defineSetter__()', scope); }).to.throw(Error, unsupportedFunctionError); }); it('disallow: object in arguments for most functions', () => { expect(() => { evaluate('[1, 2, 3].indexOf({})'); }).to.throw(Error, argumentTypeError); expect(() => { evaluate('"abc".substr({})'); }).to.throw(Error, argumentTypeError); // Only allow objects in arguments for some functions. expect(evaluate('keys({x: 2})')).to.deep.equal(['x']); expect(evaluate('values({x: 2})')).to.deep.equal([2]); expect(evaluate('splice([1, 3], 1, 0, {x: 2})')).to.deep.equal([ 1, {x: 2}, 3, ]); }); }); describe('banned', () => { it('disallow: prototype property access', () => { expect(evaluate('constructor')).to.be.null; expect(evaluate('prototype')).to.be.null; expect(evaluate('__proto__')).to.be.null; expect(evaluate('{}.constructor')).to.be.null; expect(evaluate('{}.prototype')).to.be.null; expect(evaluate('{}.__proto__')).to.be.null; expect(evaluate('[].constructor')).to.be.null; expect(evaluate('[].prototype')).to.be.null; expect(evaluate('[].__proto__')).to.be.null; expect(evaluate('"abc".constructor')).to.be.null; expect(evaluate('"abc".prototype')).to.be.null; expect(evaluate('"abc".__proto__')).to.be.null; expect(evaluate('123.constructor')).to.be.null; expect(evaluate('123.prototype')).to.be.null; expect(evaluate('123.__proto__')).to.be.null; const scope = { foo: {}, bar: [], baz: 'abc', qux: 123, }; expect(evaluate('foo.constructor', scope)).to.be.null; expect(evaluate('foo.prototype', scope)).to.be.null; expect(evaluate('foo.__proto__', scope)).to.be.null; expect(evaluate('bar.constructor', scope)).to.be.null; expect(evaluate('bar.prototype', scope)).to.be.null; expect(evaluate('bar.__proto__', scope)).to.be.null; expect(evaluate('baz.constructor', scope)).to.be.null; expect(evaluate('baz.prototype', scope)).to.be.null; expect(evaluate('baz.__proto__', scope)).to.be.null; expect(evaluate('qux.constructor', scope)).to.be.null; expect(evaluate('qux.prototype', scope)).to.be.null; expect(evaluate('qux.__proto__', scope)).to.be.null; }); it('disallow: loops', () => { expect(() => { evaluate('if (foo) "bar"', {foo: 0}); }).to.throw(); expect(() => { evaluate('switch (foo) { case 0: "bar" }', {foo: 0}); }).to.throw(); expect(() => { evaluate('for (;;) {}'); }).to.throw(); expect(() => { evaluate('while (true) {}'); }).to.throw(); expect(() => { evaluate('do {} while (true)'); }).to.throw(); expect(() => { evaluate('for (var i in foo) {}', {foo: [1, 2, 3]}); }).to.throw(); expect(() => { evaluate('for (var i of foo) {}', {foo: [1, 2, 3]}); }).to.throw(); }); /** @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects */ it('disallow: globals', () => { expect(evaluate('window')).to.be.null; expect(evaluate('arguments')).to.be.null; expect(evaluate('Infinity')).to.be.null; expect(evaluate('NaN')).to.be.null; expect(evaluate('undefined')).to.be.null; expect(() => { evaluate('eval()'); }).to.throw(); expect(() => { evaluate('uneval()'); }).to.throw(); expect(() => { evaluate('isFinite()'); }).to.throw(); expect(() => { evaluate('isNaN()'); }).to.throw(); expect(() => { evaluate('parseFloat()'); }).to.throw(); expect(() => { evaluate('parseInt()'); }).to.throw(); expect(() => { evaluate('decodeURI()'); }).to.throw(); expect(() => { evaluate('decodeURIComponent()'); }).to.throw(); expect(() => { evaluate('escape()'); }).to.throw(); expect(() => { evaluate('unescape()'); }).to.throw(); expect(evaluate('Object')).to.be.null; expect(evaluate('Function')).to.be.null; expect(evaluate('Boolean')).to.be.null; expect(evaluate('Symbol')).to.be.null; expect(evaluate('Error')).to.be.null; expect(evaluate('EvalError')).to.be.null; expect(evaluate('InternalError')).to.be.null; expect(evaluate('RangeError')).to.be.null; expect(evaluate('ReferenceError')).to.be.null; expect(evaluate('SyntaxError')).to.be.null; expect(evaluate('TypeError')).to.be.null; expect(evaluate('URIError')).to.be.null; expect(evaluate('Number')).to.be.null; expect(evaluate('Math')).to.be.null; expect(evaluate('Date')).to.be.null; expect(evaluate('String')).to.be.null; expect(evaluate('RegExp')).to.be.null; expect(evaluate('Array')).to.be.null; expect(evaluate('Int8Array')).to.be.null; expect(evaluate('Uint8Array')).to.be.null; expect(evaluate('Uint8ClampedArray')).to.be.null; expect(evaluate('Int16Array')).to.be.null; expect(evaluate('Uint16Array')).to.be.null; expect(evaluate('Int32Array')).to.be.null; expect(evaluate('Uint32Array')).to.be.null; expect(evaluate('Float32Array')).to.be.null; expect(evaluate('Float64Array')).to.be.null; expect(evaluate('Map')).to.be.null; expect(evaluate('Set')).to.be.null; expect(evaluate('WeakMap')).to.be.null; expect(evaluate('WeakSet')).to.be.null; expect(evaluate('ArrayBuffer')).to.be.null; expect(evaluate('SharedArrayBuffer')).to.be.null; expect(evaluate('Atomics')).to.be.null; expect(evaluate('DataView')).to.be.null; expect(evaluate('JSON')).to.be.null; expect(evaluate('Promise')).to.be.null; expect(evaluate('Generator')).to.be.null; expect(evaluate('GeneratorFunction')).to.be.null; expect(evaluate('AsyncFunction')).to.be.null; expect(evaluate('Reflect')).to.be.null; expect(evaluate('Proxy')).to.be.null; expect(evaluate('Intl')).to.be.null; expect(evaluate('Iterator')).to.be.null; expect(evaluate('ParallelArray')).to.be.null; expect(evaluate('StopIteration')).to.be.null; }); it('disallow: exceeding maximum AST size', () => { expect(new BindExpression('1 + 1', {}, /* maxAstSize */ 3)).to.not.be .null; // The expression '1 + 1' should have an AST size of 3 -- one for each // literal, and a PLUS expression wrapping them. expect(() => { new BindExpression('1 + 1', {}, /* maxAstSize */ 2); }).to.throw(expressionSizeExceededError); // Test size computation for macros. const add = new BindMacro({ name: 'add', argumentNames: ['x', 'y'], expressionString: 'x + y', }); expect(add.getExpressionSize()).to.equal(3); // The expression add(1, 1) should have an AST size of 3. expect(new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 3)).to.not .be.null; expect(() => { new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 2); }).to.throw(expressionSizeExceededError); // The expression add(1, 1 + 1) should have an AST size of 5. expect(new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 5)).to .not.be.null; expect(() => { new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 4); }).to.throw(expressionSizeExceededError); }); }); describe('arrow functions', () => { it('known issue: single parameters with parentheses are ambiguous', () => { // Single parameters in parentheses are ambiguous to the parser. expect(() => evaluate('[1, 2, 3].map((x) => x * x)')).to.throw(); }); it('return a non-primitive', () => { expect(evaluate('[0].map(x => ({a: x + 1}))')).to.deep.equal([{a: 1}]); expect(evaluate('[0].map(x => [x, x+1])')).to.deep.equal([[0, 1]]); }); it('Array#map()', () => { const a = [1, 2, 3]; expect(evaluate('a.map(() => 5)', {a})).to.deep.equal([5, 5, 5]); expect(evaluate('a.map(x => x + 1)', {a})).to.deep.equal([2, 3, 4]); expect(evaluate('a.map((x, i) => i * 2)', {a})).to.deep.equal([0, 2, 4]); const b = [{foo: 'x'}, {foo: 'y'}, {foo: 'z'}]; expect(evaluate('b.map(x => x.foo)', {b})).to.deep.equal(['x', 'y', 'z']); }); it('Array#reduce()', () => { const a = [1, 2, 3]; expect(evaluate('a.reduce((x, y) => x + y)', {a})).to.equal(6); expect(evaluate('a.reduce((x, y) => x + y, 4)', {a})).to.equal(10); }); it('Array#filter', () => { const a = [1, 2, 3]; expect(evaluate('a.filter(x => x > 1)', {a})).to.deep.equal([2, 3]); }); it('Array#some', () => { const a = [1, 2, 3]; expect(evaluate('a.some(x => x == 0)', {a})).to.be.false; expect(evaluate('a.some(x => x == 1)', {a})).to.be.true; }); it('disallow: usage other than as function parameter', () => { expect(() => { evaluate('() => 123'); }).to.throw(); expect(() => { evaluate('x => 123'); }).to.throw(); expect(() => { evaluate('(x, y) => 123'); }).to.throw(); expect(() => { evaluate('(() => 123).constructor()'); }).to.throw(); expect(() => { evaluate('(x => 123).constructor()'); }).to.throw(); expect(() => { evaluate('((x, y) => 123).constructor()'); }).to.throw(); expect(() => { evaluate('(() => 123).name'); }).to.throw(); expect(() => { evaluate('(x => 123).name'); }).to.throw(); expect(() => { evaluate('((x, y) => 123).name'); }).to.throw(); }); it('disallow: `arguments` or `this`', () => { const a = [1, 2, 3]; expect(evaluate('a.map(() => arguments)', {a})).to.deep.equal([ null, null, null, ]); expect(evaluate('a.reduce(() => arguments)', {a})).to.deep.equal(null); expect(evaluate('a.map(() => this)', {a})).to.deep.equal([ null, null, null, ]); expect(evaluate('a.reduce(() => this)', {a})).to.deep.equal(null); }); }); });