packages/__tests__/src/2-runtime/ast.spec.ts
import { IServiceLocator, Writable, IIndexable } from '@aurelia/kernel';
import {
eachCartesianJoin,
eachCartesianJoinFactory,
createScopeForTest,
MockTracingExpression,
MockBinding,
assert
} from '@aurelia/testing';
import {
AccessKeyedExpression,
AccessMemberExpression,
AccessScopeExpression,
AccessThisExpression,
ArrayLiteralExpression,
AssignExpression,
BinaryExpression,
BindingBehaviorExpression,
CallFunctionExpression,
CallMemberExpression,
CallScopeExpression,
ConditionalExpression,
// IsBinary,
IsBindingBehavior,
IsLeftHandSide,
// IsPrimary,
// IsUnary,
ObjectLiteralExpression,
PrimitiveLiteralExpression,
TaggedTemplateExpression,
TemplateExpression,
UnaryExpression,
ValueConverterExpression,
DestructuringAssignmentSingleExpression,
DestructuringAssignmentRestExpression,
DestructuringAssignmentExpression,
ArrowFunction,
BindingIdentifier,
Unparser,
AccessBoundaryExpression,
} from '@aurelia/expression-parser';
import { IObserverLocatorBasedConnectable } from '@aurelia/runtime';
import { type IAstEvaluator, astAssign, astEvaluate, astBind, IBinding, Scope } from '@aurelia/runtime-html';
const $false = PrimitiveLiteralExpression.$false;
const $true = PrimitiveLiteralExpression.$true;
const $null = PrimitiveLiteralExpression.$null;
const $undefined = PrimitiveLiteralExpression.$undefined;
// const $str = PrimitiveLiteralExpression.$empty;
const $arr = ArrayLiteralExpression.$empty;
const $obj = ObjectLiteralExpression.$empty;
const $tpl = TemplateExpression.$empty;
const $this = new AccessThisExpression(0);
const $parent = new AccessThisExpression(1);
const boundary = new AccessBoundaryExpression();
const dummyLocator = { get: () => null } as unknown as IServiceLocator & IAstEvaluator;
const dummyLocatorThatReturnsNull = {
get() {
return null;
},
} as unknown as IServiceLocator & IAstEvaluator;
const dummyBinding = {
observe: () => { return; },
locator: dummyLocator
} as unknown as IBinding & IObserverLocatorBasedConnectable;
const dummyBindingWithLocatorThatReturnsNull = {
observe: () => { return; },
locator: dummyLocatorThatReturnsNull,
} as unknown as IBinding & IObserverLocatorBasedConnectable;
const dummyScope = Scope.create({});
function assignDoesNotThrow(inputs: [string, IsBindingBehavior][]) {
describe('assign() does not throw / is a no-op', function () {
for (const [text, expr] of inputs) {
it(`${text}, null`, function () {
astAssign(expr, null, null, null);
});
}
});
}
function throwsOn<
TMethod extends typeof astEvaluate | typeof astAssign | typeof astBind,
>(method: TMethod, msg: string, ...args: Parameters<TMethod>): void {
let err = null;
try {
// (expr as any)[method](...args);
(method as any)(...args);
} catch (e) {
err = e;
}
assert.notStrictEqual(err, null, 'err');
if (msg?.length) {
assert.includes(err.message, msg, 'err.message.includes(msg)');
}
}
describe('2-runtime/ast.spec.ts', function () {
// const $num1 = new PrimitiveLiteralExpression(1);
// const $str1 = new PrimitiveLiteralExpression('1');
describe('[UNIT] AST', function () {
const AccessThisList: [string, AccessThisExpression][] = [
[`$this`, $this],
[`$parent`, $parent],
[`$parent.$parent`, new AccessThisExpression(2)]
];
const AccessBoundaryList: [string, AccessBoundaryExpression][] = [
[`this`, boundary],
];
const AccessScopeList: [string, AccessScopeExpression][] = [
...AccessBoundaryList,
...AccessThisList.map(([input, expr]) => [`${input}.a`, new AccessScopeExpression('a', expr.ancestor)] as [string, any]),
[`$this.$parent`, new AccessScopeExpression('$parent')],
[`$host.$parent`, new AccessScopeExpression('$parent', undefined)],
[`$parent.$this`, new AccessScopeExpression('$this', 1)],
[`a`, new AccessScopeExpression('a')]
];
const StringLiteralList: [string, PrimitiveLiteralExpression][] = [
[`''`, PrimitiveLiteralExpression.$empty]
];
const NumberLiteralList: [string, PrimitiveLiteralExpression][] = [
[`1`, new PrimitiveLiteralExpression(1)],
[`1.1`, new PrimitiveLiteralExpression(1.1)],
[`.1`, new PrimitiveLiteralExpression(0.1)],
[`0.1`, new PrimitiveLiteralExpression(0.1)]
];
const KeywordLiteralList: [string, PrimitiveLiteralExpression][] = [
[`undefined`, $undefined],
[`null`, $null],
[`true`, $true],
[`false`, $false]
];
// const PrimitiveLiteralList: [string, PrimitiveLiteralExpression][] = [
// ...StringLiteralList,
// ...NumberLiteralList,
// ...KeywordLiteralList
// ];
const ArrayLiteralList: [string, ArrayLiteralExpression][] = [
[`[]`, $arr]
];
const ObjectLiteralList: [string, ObjectLiteralExpression][] = [
[`{}`, $obj]
];
const TemplateLiteralList: [string, TemplateExpression][] = [
[`\`\``, $tpl]
];
// const LiteralList: [string, IsPrimary][] = [
// ...PrimitiveLiteralList,
// ...TemplateLiteralList,
// ...ArrayLiteralList,
// ...ObjectLiteralList
// ];
const TemplateInterpolationList: [string, TemplateExpression][] = [
[`\`\${a}\``, new TemplateExpression(['', ''], [new AccessScopeExpression('a')])]
];
// const PrimaryList: [string, IsPrimary][] = [
// ...AccessThisList,
// ...AccessScopeList,
// ...LiteralList
// ];
// 2. parseMemberExpression.MemberExpression [ AssignmentExpression ]
// const SimpleAccessKeyedList: [string, IsLeftHandSide][] = [
// ...AccessScopeList
// .map(([input, expr]) => [`${input}[b]`, new AccessKeyedExpression(expr, new AccessScopeExpression('b'))] as [string, any])
// ];
// 3. parseMemberExpression.MemberExpression . IdentifierName
// const SimpleAccessMemberList: [string, IsLeftHandSide][] = [
// ...AccessScopeList
// .map(([input, expr]) => [`${input}.b`, new AccessMemberExpression(expr, 'b')] as [string, any])
// ];
// 4. parseMemberExpression.MemberExpression TemplateLiteral
const SimpleTaggedTemplateList: [string, IsLeftHandSide][] = [
...AccessScopeList
.map(([input, expr]) => [`${input}\`\``, new TaggedTemplateExpression([''], [''], expr, [])] as [string, any]),
...AccessScopeList
.map(([input, expr]) => [`${input}\`\${a}\``, new TaggedTemplateExpression(['', ''], ['', ''], expr, [new AccessScopeExpression('a')])] as [string, any])
];
// 1. parseCallExpression.MemberExpression Arguments (this one doesn't technically fit the spec here)
const SimpleCallFunctionList: [string, IsLeftHandSide][] = [
...AccessScopeList
.map(([input, expr]) => [`${input}()`, new CallFunctionExpression(expr, [])] as [string, any])
];
// 2. parseCallExpression.MemberExpression Arguments
const SimpleCallScopeList: [string, IsLeftHandSide][] = [
...AccessScopeList
.map(([input, expr]) => [`${input}()`, new CallScopeExpression((expr as any).name, [], expr.ancestor)] as [string, any])
];
// 3. parseCallExpression.MemberExpression Arguments
const SimpleCallMemberList: [string, IsLeftHandSide][] = [
...AccessScopeList
.map(([input, expr]) => [`${input}.b()`, new CallMemberExpression(expr, 'b', [])] as [string, any])
];
// concatenation of 1-3 of MemberExpression and 1-3 of CallExpression
// const SimpleLeftHandSideList: [string, IsLeftHandSide][] = [
// ...SimpleAccessKeyedList,
// ...SimpleAccessMemberList,
// ...SimpleTaggedTemplateList,
// ...SimpleCallFunctionList,
// ...SimpleCallScopeList,
// ...SimpleCallMemberList
// ];
// concatenation of Primary and Member+CallExpression
// This forms the group Precedence.LeftHandSide
// used only for testing complex UnaryExpression expressions
// const SimpleIsLeftHandSideList: [string, IsLeftHandSide][] = [
// ...PrimaryList,
// ...SimpleLeftHandSideList
// ];
// parseUnaryExpression (this is actually at the top in the parser due to the order in which expressions must be parsed)
const SimpleUnaryList: [string, UnaryExpression][] = [
[`!$1`, new UnaryExpression('!', new AccessScopeExpression('$1'))],
[`-$2`, new UnaryExpression('-', new AccessScopeExpression('$2'))],
[`+$3`, new UnaryExpression('+', new AccessScopeExpression('$3'))],
[`void $4`, new UnaryExpression('void', new AccessScopeExpression('$4'))],
[`typeof $5`, new UnaryExpression('typeof', new AccessScopeExpression('$5'))]
];
// concatenation of UnaryExpression + LeftHandSide
// This forms the group Precedence.LeftHandSide and includes Precedence.UnaryExpression
// const SimpleIsUnaryList: [string, IsUnary][] = [
// ...SimpleIsLeftHandSideList,
// ...SimpleUnaryList
// ];
// This forms the group Precedence.Multiplicative
const SimpleMultiplicativeList: [string, BinaryExpression][] = [
[`$6*$7`, new BinaryExpression('*', new AccessScopeExpression('$6'), new AccessScopeExpression('$7'))],
[`$8%$9`, new BinaryExpression('%', new AccessScopeExpression('$8'), new AccessScopeExpression('$9'))],
[`$10/$11`, new BinaryExpression('/', new AccessScopeExpression('$10'), new AccessScopeExpression('$11'))]
];
// const SimpleIsMultiplicativeList: [string, IsBinary][] = [
// ...SimpleIsUnaryList,
// ...SimpleMultiplicativeList
// ];
// This forms the group Precedence.Additive
const SimpleAdditiveList: [string, BinaryExpression][] = [
[`$12+$13`, new BinaryExpression('+', new AccessScopeExpression('$12'), new AccessScopeExpression('$13'))],
[`$14-$15`, new BinaryExpression('-', new AccessScopeExpression('$14'), new AccessScopeExpression('$15'))]
];
// const SimpleIsAdditiveList: [string, IsBinary][] = [
// ...SimpleIsMultiplicativeList,
// ...SimpleAdditiveList
// ];
// This forms the group Precedence.Relational
const SimpleRelationalList: [string, BinaryExpression][] = [
[`$16<$17`, new BinaryExpression('<', new AccessScopeExpression('$16'), new AccessScopeExpression('$17'))],
[`$18>$19`, new BinaryExpression('>', new AccessScopeExpression('$18'), new AccessScopeExpression('$19'))],
[`$20<=$21`, new BinaryExpression('<=', new AccessScopeExpression('$20'), new AccessScopeExpression('$21'))],
[`$22>=$23`, new BinaryExpression('>=', new AccessScopeExpression('$22'), new AccessScopeExpression('$23'))],
[`$24 in $25`, new BinaryExpression('in', new AccessScopeExpression('$24'), new AccessScopeExpression('$25'))],
[`$26 instanceof $27`, new BinaryExpression('instanceof', new AccessScopeExpression('$26'), new AccessScopeExpression('$27'))]
];
// const SimpleIsRelationalList: [string, IsBinary][] = [
// ...SimpleIsAdditiveList,
// ...SimpleRelationalList
// ];
// This forms the group Precedence.Equality
const SimpleEqualityList: [string, BinaryExpression][] = [
[`$28==$29`, new BinaryExpression('==', new AccessScopeExpression('$28'), new AccessScopeExpression('$29'))],
[`$30!=$31`, new BinaryExpression('!=', new AccessScopeExpression('$30'), new AccessScopeExpression('$31'))],
[`$32===$33`, new BinaryExpression('===', new AccessScopeExpression('$32'), new AccessScopeExpression('$33'))],
[`$34!==$35`, new BinaryExpression('!==', new AccessScopeExpression('$34'), new AccessScopeExpression('$35'))]
];
// const SimpleIsEqualityList: [string, IsBinary][] = [
// ...SimpleIsRelationalList,
// ...SimpleEqualityList
// ];
// This forms the group Precedence.LogicalAND
const SimpleLogicalANDList: [string, BinaryExpression][] = [
[`$36&&$37`, new BinaryExpression('&&', new AccessScopeExpression('$36'), new AccessScopeExpression('$37'))]
];
// This forms the group Precedence.LogicalOR
const SimpleLogicalORList: [string, BinaryExpression][] = [
[`$38||$39`, new BinaryExpression('||', new AccessScopeExpression('$38'), new AccessScopeExpression('$39'))]
];
// This forms the group Precedence.ConditionalExpression
const SimpleConditionalList: [string, ConditionalExpression][] = [
[`a?b:c`, new ConditionalExpression(new AccessScopeExpression('a'), new AccessScopeExpression('b'), new AccessScopeExpression('c'))]
];
// This forms the group Precedence.AssignExpression
// const SimpleAssignList: [string, AssignExpression][] = [
// [`a=b`, new AssignExpression(new AccessScopeExpression('a'), new AccessScopeExpression('b'))]
// ];
// This forms the group Precedence.Variadic
const SimpleValueConverterList: [string, ValueConverterExpression][] = [
[`a|b`, new ValueConverterExpression(new AccessScopeExpression('a'), 'b', [])],
[`a|b:c`, new ValueConverterExpression(new AccessScopeExpression('a'), 'b', [new AccessScopeExpression('c')])],
[`a|b:c:d`, new ValueConverterExpression(new AccessScopeExpression('a'), 'b', [new AccessScopeExpression('c'), new AccessScopeExpression('d')])]
];
const SimpleBindingBehaviorList: [string, BindingBehaviorExpression][] = [
[`a&b`, new BindingBehaviorExpression(new AccessScopeExpression('a'), 'b', [])],
[`a&b:c`, new BindingBehaviorExpression(new AccessScopeExpression('a'), 'b', [new AccessScopeExpression('c')])],
[`a&b:c:d`, new BindingBehaviorExpression(new AccessScopeExpression('a'), 'b', [new AccessScopeExpression('c'), new AccessScopeExpression('d')])]
];
describe('Literals', function () {
describe('evaluate() works without any input', function () {
for (const [text, expr] of [
...StringLiteralList,
...NumberLiteralList,
...KeywordLiteralList
]) {
it(text, function () {
assert.strictEqual(astEvaluate(expr, undefined, undefined, null), expr.value, `astEvaluate(expr, undefined, undefined)`);
});
}
for (const [text, expr] of TemplateLiteralList) {
it(text, function () {
assert.strictEqual(astEvaluate(expr, undefined, undefined, null), '', `astEvaluate(expr, undefined, undefined)`);
});
}
for (const [text, expr] of ArrayLiteralList) {
it(text, function () {
assert.instanceOf(astEvaluate(expr, undefined, undefined, null), Array, 'astEvaluate(expr, undefined, undefined)');
});
}
for (const [text, expr] of ObjectLiteralList) {
it(text, function () {
assert.instanceOf(astEvaluate(expr, undefined, undefined, null), Object, 'astEvaluate(expr, undefined, undefined)');
});
}
});
assignDoesNotThrow([
...StringLiteralList,
...NumberLiteralList,
...KeywordLiteralList,
...TemplateLiteralList,
...ArrayLiteralList,
...ObjectLiteralList
]);
});
describe('Context Accessors', function () {
assignDoesNotThrow(AccessThisList);
});
describe('Scope Accessors', function () {
assignDoesNotThrow([
...TemplateInterpolationList,
...SimpleTaggedTemplateList
]);
});
describe('CallExpression', function () {
assignDoesNotThrow([
...SimpleCallFunctionList,
...SimpleCallScopeList,
...SimpleCallMemberList
]);
});
describe('UnaryExpression', function () {
assignDoesNotThrow(SimpleUnaryList);
});
describe('BinaryExpression', function () {
const SimplyBinaryList = [
...SimpleMultiplicativeList,
...SimpleAdditiveList,
...SimpleRelationalList,
...SimpleEqualityList,
...SimpleLogicalANDList,
...SimpleLogicalORList
];
assignDoesNotThrow(SimplyBinaryList);
});
describe('ConditionalExpression', function () {
assignDoesNotThrow(SimpleConditionalList);
});
// describe('AssignExpression', function () {
// });
describe('ValueConverterExpression', function () {
describe('evaluate() throws when returned converter is null', function () {
for (const [text, expr] of SimpleValueConverterList) {
it(`${text}, undefined`, function () {
throwsOn(astEvaluate, `AUR0103:b`, expr, dummyScope, dummyLocatorThatReturnsNull, null);
// throwsOn(expr, 'evaluate', `ValueConverter named 'b' could not be found. Did you forget to register it as a dependency?`, LF.none, dummyScope, dummyLocatorThatReturnsNull, null);
});
}
});
describe('assign() throws when returned converter is null', function () {
for (const [text, expr] of SimpleValueConverterList) {
it(`${text}, null`, function () {
throwsOn(astAssign, `AUR0103:b`, expr, dummyScope, dummyLocatorThatReturnsNull, null);
// throwsOn(expr, 'assign', `ValueConverter named 'b' could not be found. Did you forget to register it as a dependency?`, LF.none, dummyScope, dummyLocatorThatReturnsNull, null);
});
}
});
});
describe('BindingBehaviorExpression', function () {
describe('bind() throws when returned behavior is null', function () {
for (const [text, expr] of SimpleBindingBehaviorList) {
it(`${text}, undefined`, function () {
throwsOn(astBind, `AUR0101:b`, expr, dummyScope, dummyBindingWithLocatorThatReturnsNull);
// throwsOn(expr, 'bind', `BindingBehavior named 'b' could not be found. Did you forget to register it as a dependency?`, LF.none, dummyScope, dummyBindingWithLocatorThatReturnsNull);
});
}
});
});
});
describe('AccessKeyedExpression', function () {
let expression: AccessKeyedExpression;
before(function () {
expression = new AccessKeyedExpression(new AccessScopeExpression('foo', 0), new PrimitiveLiteralExpression('bar'));
});
it('evaluates member on bindingContext', function () {
const scope = createScopeForTest({ foo: { bar: 'baz' } });
assert.strictEqual(astEvaluate(expression, scope, null, null), 'baz', `astEvaluate(expression, scope, null)`);
});
it('evaluates member on overrideContext', function () {
const scope = createScopeForTest({});
scope.overrideContext.foo = { bar: 'baz' };
assert.strictEqual(astEvaluate(expression, scope, null, null), 'baz', `astEvaluate(expression, scope, null)`);
});
it('assigns member on bindingContext', function () {
const scope = createScopeForTest({ foo: { bar: 'baz' } });
astAssign(expression, scope, null, 'bang');
assert.strictEqual((scope.bindingContext.foo as IIndexable).bar, 'bang', `(scope.bindingContext.foo as IIndexable).bar`);
});
it('assigns member on overrideContext', function () {
const scope = createScopeForTest({});
scope.overrideContext.foo = { bar: 'baz' };
astAssign(expression, scope, null, 'bang');
assert.strictEqual((scope.overrideContext.foo as IIndexable).bar, 'bang', `(scope.overrideContext.foo as IIndexable).bar`);
});
it('evaluates null/undefined object', function () {
let scope = createScopeForTest({ foo: null });
assert.strictEqual(astEvaluate(expression, scope, null, null), undefined, `astEvaluate(expression, scope, null, null)`);
scope = createScopeForTest({ foo: undefined });
assert.strictEqual(astEvaluate(expression, scope, null, null), undefined, `astEvaluate(expression, scope, null, null)`);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), undefined, `astEvaluate(expression, scope, null, null)`);
});
it('does not observes property in keyed object access when key is number', function () {
const scope = createScopeForTest({ foo: { '0': 'hello world' } });
const expression2 = new AccessKeyedExpression(new AccessScopeExpression('foo', 0), new PrimitiveLiteralExpression(0));
assert.strictEqual(astEvaluate(expression2, scope, null, null), 'hello world', `astEvaluate(expression2, scope, null)`);
const binding = new MockBinding();
astEvaluate(expression2, scope, dummyLocator, binding);
assert.deepStrictEqual(binding.calls[0], ['observe', scope.bindingContext, 'foo'], 'binding.calls[0]');
assert.deepStrictEqual(binding.calls[1], ['observe', scope.bindingContext.foo, 0], 'binding.calls[1]');
assert.strictEqual(binding.calls.length, 2, 'binding.calls.length');
});
it('observes property in keyed array access when key is number', function () {
const scope = createScopeForTest({ foo: ['hello world'] });
const expression3 = new AccessKeyedExpression(new AccessScopeExpression('foo', 0), new PrimitiveLiteralExpression(0));
assert.strictEqual(astEvaluate(expression3,scope, null, null), 'hello world', `astEvaluate(expression3,scope, null)`);
const binding = new MockBinding();
astEvaluate(expression3,scope, dummyLocator, binding);
assert.deepStrictEqual(binding.calls[0], ['observe', scope.bindingContext, 'foo'], 'binding.calls[0]');
assert.strictEqual(binding.calls.length, 2, 'binding.calls.length');
});
describe('returns the right value when accessing keyed on primitive', function () {
it('returns string when accessing string character', function () {
const value = astEvaluate(
new AccessKeyedExpression(new PrimitiveLiteralExpression('a'), new PrimitiveLiteralExpression(0)),
null,
null,
null
);
assert.strictEqual(value, 'a');
});
it('returns undefined when accessing keyed on null/undefined', function () {
const value = astEvaluate(
new AccessKeyedExpression(PrimitiveLiteralExpression.$null, new PrimitiveLiteralExpression(0)),
null,
null,
null
);
assert.strictEqual(value, undefined);
});
it('returns prototype method when accessing keyed on primitive', function () {
const value = astEvaluate(
new AccessKeyedExpression(new PrimitiveLiteralExpression(0), new PrimitiveLiteralExpression('toFixed')),
null,
null,
null
);
assert.strictEqual(value, Number.prototype.toFixed);
});
});
describe('does not attempt to observe property when object is primitive', function () {
const objects: [string, any][] = [
[` null`, null],
[`undefined`, undefined],
[` ''`, ''],
[`1`, 1],
[` true`, true],
[` false`, false],
[` Symbol()`, Symbol()]
];
const keys: [string, any][] = [
[`[0] `, new PrimitiveLiteralExpression(0)],
[`['a']`, new PrimitiveLiteralExpression('a')]
];
const inputs: [typeof objects, typeof keys] = [objects, keys];
eachCartesianJoin(inputs, (([t1, obj], [t2, key]) => {
it(`${t1}${t2}`, function () {
const scope = createScopeForTest({ foo: obj });
const sut = new AccessKeyedExpression(new AccessScopeExpression('foo', 0), key);
const binding = new MockBinding();
astEvaluate(sut, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1);
assert.strictEqual(binding.calls[0][0], 'observe');
});
}));
});
});
describe('AccessMemberExpression', function () {
const objects: (() => [string, any, boolean, boolean])[] = [
() => [` null`, null, true, false],
() => [`undefined`, undefined, true, false],
() => [` ''`, '', true, false],
() => [` 'a'`, 'a', false, false],
() => [` false`, false, true, false],
() => [` 1`, 1, false, false],
() => [` true`, true, false, false],
() => [` Symbol()`, Symbol(), false, false],
() => [` {}`, {}, false, true],
() => [` []`, [], false, true]
];
const props: ((input: [string, any, boolean, boolean]) => [string, any, any])[] = [
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = null as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`null={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = undefined as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`undefined={}`, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = '' as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`''={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = 'a' as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`'a'={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = false as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`false={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = 1 as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`1={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = true as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`true={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = Symbol() as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`Symbol()={} `, prop, value];
},
([_$11, obj, _isFalsey, canHaveProperty]) => {
const prop = {} as any;
const value = {};
if (canHaveProperty) {
obj[prop] = value;
}
return [`{}={} `, prop, value];
},
];
const inputs: [typeof objects, typeof props] = [objects, props];
const expression: AccessMemberExpression = new AccessMemberExpression(new AccessScopeExpression('foo', 0), 'bar');
eachCartesianJoinFactory.call(this, inputs, (([t1, obj, _isFalsey, canHaveProperty], [t2, prop, value]) => {
it(`STRICT - ${t1}.${t2} evaluate() -> eval + connect -> assign`, function () {
const scope = createScopeForTest({ foo: obj });
const evaluator = { strict: true } as unknown as IAstEvaluator;
const sut = new AccessMemberExpression(new AccessScopeExpression('foo', 0), prop);
const actual = astEvaluate(sut, scope, evaluator , null);
if (canHaveProperty) {
assert.strictEqual(actual, value, `actual`);
} else {
assert.strictEqual(actual, undefined, `actual`);
}
const binding = new MockBinding();
astEvaluate(sut, scope, dummyLocator, binding);
if (canHaveProperty) {
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 2, `binding.calls.filter(c => c[0] === 'observe').length`);
} else {
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 1, `binding.calls.filter(c => c[0] === 'observe').length`);
}
if (!(obj instanceof Object)) {
assert.notInstanceOf(scope.bindingContext['foo'], Object, `scope.bindingContext['foo']`);
astAssign(sut, scope, null, 42);
assert.instanceOf(scope.bindingContext['foo'], Object, `scope.bindingContext['foo']`);
assert.strictEqual((scope.bindingContext['foo'] as IIndexable)[prop], 42, `(scope.bindingContext['foo'] as IIndexable)[prop]`);
}
});
it(`${t1}.${t2} evaluate() + connect() -> assign`, function () {
const scope = createScopeForTest({ foo: obj });
const evaluator = { strict: false } as unknown as IAstEvaluator;
const sut = new AccessMemberExpression(new AccessScopeExpression('foo', 0), prop);
const actual = astEvaluate(sut, scope, evaluator, null);
if (canHaveProperty) {
if (obj == null) {
assert.strictEqual(actual, '', `actual`);
} else {
assert.strictEqual(actual, value, `actual`);
}
} else {
if (obj == null) {
assert.strictEqual(actual, '', `actual`);
}
}
const binding = new MockBinding();
astEvaluate(sut, scope, dummyLocator, binding);
if (canHaveProperty) {
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 2, `binding.calls.filter(c => c[0] === 'observe').length`);
} else {
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 1, `binding.calls.filter(c => c[0] === 'observe').length`);
}
if (!(obj instanceof Object)) {
assert.notInstanceOf(scope.bindingContext['foo'], Object, `scope.bindingContext['foo']`);
astAssign(sut, scope, null, 42);
assert.instanceOf(scope.bindingContext['foo'], Object, `scope.bindingContext['foo']`);
assert.strictEqual((scope.bindingContext['foo'] as IIndexable)[prop], 42, `(scope.bindingContext['foo'] as IIndexable)[prop]`);
}
});
})
);
it('evaluates member on bindingContext', function () {
const scope = createScopeForTest({ foo: { bar: 'baz' } });
assert.strictEqual(astEvaluate(expression, scope, null, null), 'baz', `astEvaluate(expression, scope, null, null)`);
});
it('evaluates member on overrideContext', function () {
const scope = createScopeForTest({});
scope.overrideContext.foo = { bar: 'baz' };
assert.strictEqual(astEvaluate(expression, scope, null, null), 'baz', `astEvaluate(expression, scope, null)`);
});
it('assigns member on bindingContext', function () {
const scope = createScopeForTest({ foo: { bar: 'baz' } });
astAssign(expression, scope, null, 'bang');
assert.strictEqual((scope.bindingContext.foo as IIndexable).bar, 'bang', `(scope.bindingContext.foo as IIndexable).bar`);
});
it('assigns member on overrideContext', function () {
const scope = createScopeForTest({});
scope.overrideContext.foo = { bar: 'baz' };
astAssign(expression, scope, null, 'bang');
assert.strictEqual((scope.overrideContext.foo as IIndexable).bar, 'bang', `(scope.overrideContext.foo as IIndexable).bar`);
});
it('returns the assigned value', function () {
const scope = createScopeForTest({ foo: { bar: 'baz' } });
assert.strictEqual(astAssign(expression, scope, null, 'bang'), 'bang', `astAssign(expression, scope, null, 'bang')`);
});
describe('does not attempt to observe property when object is falsey', function () {
const objects2: [string, any][] = [
[` null`, null],
[`undefined`, undefined],
[` ''`, ''],
[` false`, false]
];
const props2: [string, any][] = [
[`.0`, 0],
[`.a`, 'a']
];
const inputs2: [typeof objects2, typeof props2, boolean[]] = [objects2, props2, [true, false]];
eachCartesianJoin(inputs2, (([t1, obj], [t2, prop]) => {
it(`${t1}${t2}`, function () {
const scope = createScopeForTest({ foo: obj });
const sut = new AccessMemberExpression(new AccessScopeExpression('foo', 0), prop);
const binding = new MockBinding();
astEvaluate(sut, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 1, `binding.calls.filter(c => c[0] === 'observe').length`);
});
}));
});
describe('does not observe if object does not / cannot have the property', function () {
const objects3: [string, any][] = [
[` 1`, 1],
[` true`, true],
[` Symbol()`, Symbol()]
];
const props3: [string, any][] = [
[`.0`, 0],
[`.a`, 'a']
];
const inputs3: [typeof objects3, typeof props3, boolean[]] = [objects3, props3, [true, false]];
eachCartesianJoin(inputs3, (([t1, obj], [t2, prop]) => {
it(`${t1}${t2}`, function () {
const scope = createScopeForTest({ foo: obj });
const expression2 = new AccessMemberExpression(new AccessScopeExpression('foo', 0), prop);
const binding = new MockBinding();
astEvaluate(expression2, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 1, `binding.calls.filter(c => c[0] === 'observe').length`);
});
}));
});
});
describe('AccessScopeExpression', function () {
const foo: AccessScopeExpression = new AccessScopeExpression('foo', 0);
const $parentfoo: AccessScopeExpression = new AccessScopeExpression('foo', 1);
it(`evaluates defined property on bindingContext`, function () {
const scope: Scope = createScopeForTest({ foo: 'bar' });
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
});
it(`evaluates defined property on overrideContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' });
scope.overrideContext.foo = 'bar';
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
});
it(`assigns defined property on bindingContext`, function () {
const scope = createScopeForTest({ foo: 'bar' });
astAssign(foo, scope, null, 'baz');
assert.strictEqual(scope.bindingContext.foo, 'baz', `scope.bindingContext.foo`);
});
it(`assigns undefined property to bindingContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' });
astAssign(foo, scope, null, 'baz');
assert.strictEqual(scope.bindingContext.foo, 'baz', `scope.bindingContext.foo`);
});
it(`assigns defined property on overrideContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' });
scope.overrideContext.foo = 'bar';
astAssign(foo, scope, null, 'baz');
assert.strictEqual(scope.overrideContext.foo, 'baz', `scope.overrideContext.foo`);
});
it(`connects defined property on bindingContext`, function () {
const scope = createScopeForTest({ foo: 'bar' });
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.bindingContext, 'foo'], 'binding.calls[0]');
});
it(`connects defined property on overrideContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' });
scope.overrideContext.foo = 'bar';
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.overrideContext, 'foo'], 'binding.calls[0]');
});
it(`connects undefined property on bindingContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' });
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.bindingContext, 'foo'], 'binding.calls[0]');
});
it(`evaluates defined property on first ancestor bindingContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, { foo: 'bar' });
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
assert.strictEqual(astEvaluate($parentfoo, scope, null, null), 'bar', `astEvaluate($parentfoo, scope, null, null)`);
});
it(`evaluates defined property on first ancestor overrideContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, { def: 'rsw' });
scope.parent.overrideContext.foo = 'bar';
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null)`);
assert.strictEqual(astEvaluate($parentfoo, scope, null, null), 'bar', `astEvaluate($parentfoo, scope, null)`);
});
it(`assigns defined property on first ancestor bindingContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, { foo: 'bar' });
astAssign(foo, scope, null, 'baz');
assert.strictEqual(scope.parent.bindingContext.foo, 'baz', `scope.parent.bindingContext.foo`);
astAssign($parentfoo, scope, null, 'beep');
assert.strictEqual(scope.parent.bindingContext.foo, 'beep', `scope.parent.bindingContext.foo`);
});
it(`assigns defined property on first ancestor overrideContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, { def: 'rsw' });
scope.parent.overrideContext.foo = 'bar';
astAssign(foo, scope, null, 'baz');
assert.strictEqual(scope.parent.overrideContext.foo, 'baz', `scope.parent.overrideContext.foo`);
astAssign($parentfoo, scope, null, 'beep');
assert.strictEqual(scope.parent.overrideContext.foo, 'beep', `scope.parent.overrideContext.foo`);
});
it(`connects defined property on first ancestor bindingContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, { foo: 'bar' });
let binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.bindingContext, 'foo'], 'binding.calls[0]');
binding = new MockBinding();
astEvaluate($parentfoo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.bindingContext, 'foo'], 'binding.calls[0]');
});
it(`connects defined property on first ancestor overrideContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, { def: 'rsw' });
scope.parent.overrideContext.foo = 'bar';
let binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.overrideContext, 'foo'], 'binding.calls[0]');
binding = new MockBinding();
astEvaluate($parentfoo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.overrideContext, 'foo'], 'binding.calls[0]');
});
it(`connects undefined property on first ancestor bindingContext`, function () {
const scope = createScopeForTest({ abc: 'xyz' }, {});
(scope.parent as Writable<Scope>).parent = Scope.create({}, { foo: 'bar' });
const binding = new MockBinding();
astEvaluate($parentfoo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.bindingContext, 'foo'], 'binding.calls[0]');
});
});
describe('AccessBoundaryExpression', function () {
it('evaluates scope boundary', function () {
const a = { a: 'a' };
const b = { b: 'b' };
const c = { c: 'c' };
const d = { d: 'd' };
let scope: Scope = Scope.create(a, null, true);
assert.strictEqual(astEvaluate(boundary, scope, null, null), a, `astEvaluate(boundary, scope, null)`);
scope = Scope.fromParent(Scope.create(b, null, true), a);
assert.strictEqual(astEvaluate(boundary, scope, null, null), b, `astEvaluate(boundary, scope, null)`);
scope = Scope.fromParent(Scope.fromParent(Scope.create(c, null, true), b), a);
assert.strictEqual(astEvaluate(boundary, scope, null, null), c, `astEvaluate(boundary, scope, null)`);
scope = Scope.fromParent(Scope.fromParent(Scope.fromParent(Scope.create(d, null, true), c), b), a);
assert.strictEqual(astEvaluate(boundary, scope, null, null), d, `astEvaluate(boundary, scope, null)`);
});
});
describe('AccessThisExpression', function () {
const $parent$parent = new AccessThisExpression(2);
const $parent$parent$parent = new AccessThisExpression(3);
it('evaluates defined bindingContext', function () {
const a = { a: 'a' };
const b = { b: 'b' };
const c = { c: 'c' };
const d = { d: 'd' };
let scope: Scope = Scope.create(a);
assert.strictEqual(astEvaluate($parent, scope, null, null), undefined, `astEvaluate($parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent, scope, null, null), undefined, `astEvaluate($parent$parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent$parent, scope, null, null), undefined, `astEvaluate($parent$parent$parent, scope, null)`);
scope = Scope.fromParent(Scope.create(b), a);
assert.strictEqual(astEvaluate($parent, scope, null, null), b, `astEvaluate($parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent, scope, null, null), undefined, `astEvaluate($parent$parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent$parent, scope, null, null), undefined, `astEvaluate($parent$parent$parent, scope, null)`);
scope = Scope.fromParent(Scope.fromParent(Scope.create(c), b), a);
assert.strictEqual(astEvaluate($parent, scope, null, null), b, `astEvaluate($parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent, scope, null, null), c, `astEvaluate($parent$parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent$parent, scope, null, null), undefined, `astEvaluate($parent$parent$parent, scope, null)`);
scope = Scope.fromParent(Scope.fromParent(Scope.fromParent(Scope.create(d), c), b), a);
assert.strictEqual(astEvaluate($parent, scope, null, null), b, `astEvaluate($parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent, scope, null, null), c, `astEvaluate($parent$parent, scope, null)`);
assert.strictEqual(astEvaluate($parent$parent$parent, scope, null, null), d, `astEvaluate($parent$parent$parent, scope, null)`);
});
});
describe('AssignExpression', function () {
it('can chain assignments', function () {
const foo = new AssignExpression(new AccessScopeExpression('foo', 0), new AccessScopeExpression('bar', 0));
const scope = Scope.create({});
astAssign(foo, scope, null, 1);
assert.strictEqual(scope.bindingContext.foo, 1, `scope.overrideContext.foo`);
assert.strictEqual(scope.bindingContext.bar, 1, `scope.overrideContext.bar`);
});
});
describe('ConditionalExpression', function () {
it('evaluates the "yes" branch', function () {
const condition = $true;
const yes = new MockTracingExpression($obj);
const no = new MockTracingExpression($obj);
const sut = new ConditionalExpression(condition, yes as any, no as any);
astEvaluate(sut, null, null, null);
assert.strictEqual(yes.calls.length, 1, `yes.calls.length`);
assert.strictEqual(no.calls.length, 0, `no.calls.length`);
});
it('evaluates the "no" branch', function () {
const condition = $false;
const yes = new MockTracingExpression($obj);
const no = new MockTracingExpression($obj);
const sut = new ConditionalExpression(condition, yes as any, no as any);
astEvaluate(sut, null, null, null);
assert.strictEqual(yes.calls.length, 0, `yes.calls.length`);
assert.strictEqual(no.calls.length, 1, `no.calls.length`);
});
it('connects the "yes" branch', function () {
const condition = $true;
const yes = new MockTracingExpression($obj);
const no = new MockTracingExpression($obj);
const sut = new ConditionalExpression(condition, yes as any, no as any);
astEvaluate(sut, null, dummyLocator, dummyBinding);
assert.strictEqual(yes.calls.length, 1, `yes.calls.length`);
assert.strictEqual(no.calls.length, 0, `no.calls.length`);
});
it('connects the "no" branch', function () {
const condition = $false;
const yes = new MockTracingExpression($obj);
const no = new MockTracingExpression($obj);
const sut = new ConditionalExpression(condition, yes as any, no as any);
astEvaluate(sut, null, dummyLocator, dummyBinding);
assert.strictEqual(yes.calls.length, 0, `yes.calls.length`);
assert.strictEqual(no.calls.length, 1, `no.calls.length`);
});
});
describe('BinaryExpression', function () {
it(`concats strings`, function () {
let expression = new BinaryExpression('+', new PrimitiveLiteralExpression('a'), new PrimitiveLiteralExpression('b'));
let scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 'ab', `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression('a'), $null);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 'a', `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', $null, new PrimitiveLiteralExpression('b'));
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 'b', `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression('a'), $undefined);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 'a', `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', $undefined, new PrimitiveLiteralExpression('b'));
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 'b', `astEvaluate(expression, scope, null, null)`);
});
it(`adds numbers`, function () {
let expression = new BinaryExpression('+', new PrimitiveLiteralExpression(1), new PrimitiveLiteralExpression(2));
let scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 3, `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression(1), $null);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 1, `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', $null, new PrimitiveLiteralExpression(2));
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, null, null), 2, `astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression(1), $undefined);
scope = createScopeForTest({});
assert.strictEqual(isNaN(astEvaluate(expression, scope, null, null) as number), false, `isNaN(astEvaluate(expression, scope, null, null)`);
expression = new BinaryExpression('+', $undefined, new PrimitiveLiteralExpression(2));
scope = createScopeForTest({});
assert.strictEqual(isNaN(astEvaluate(expression, scope, null, null) as number), false, `isNaN(astEvaluate(expression, scope, null, null)`);
});
it(`concats strings - STRICT`, function () {
let expression = new BinaryExpression('+', new PrimitiveLiteralExpression('a'), new PrimitiveLiteralExpression('b'));
let scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 'ab', `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression('a'), $null);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 'anull', `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', $null, new PrimitiveLiteralExpression('b'));
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 'nullb', `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression('a'), $undefined);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 'aundefined', `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', $undefined, new PrimitiveLiteralExpression('b'));
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 'undefinedb', `astEvaluate(expression, scope, { strict: true }, null)`);
});
it(`adds numbers - STRICT`, function () {
let expression = new BinaryExpression('+', new PrimitiveLiteralExpression(1), new PrimitiveLiteralExpression(2));
let scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 3, `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression(1), $null);
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 1, `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', $null, new PrimitiveLiteralExpression(2));
scope = createScopeForTest({});
assert.strictEqual(astEvaluate(expression, scope, { strict: true }, null), 2, `astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', new PrimitiveLiteralExpression(1), $undefined);
scope = createScopeForTest({});
assert.strictEqual(isNaN(astEvaluate(expression, scope, { strict: true }, null) as number), true, `isNaN(astEvaluate(expression, scope, { strict: true }, null)`);
expression = new BinaryExpression('+', $undefined, new PrimitiveLiteralExpression(2));
scope = createScopeForTest({});
assert.strictEqual(isNaN(astEvaluate(expression, scope, { strict: true }, null) as number), true, `isNaN(astEvaluate(expression, scope, { strict: true }, null)`);
});
it('handles 1 >= 1', function () {
const expression = new BinaryExpression('>=', new PrimitiveLiteralExpression(1), new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), true);
});
it('handles 2 >= 1', function () {
const expression = new BinaryExpression('>=', new PrimitiveLiteralExpression(2), new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), true);
});
it('handles 1 >= 2', function () {
const expression = new BinaryExpression('>=', new PrimitiveLiteralExpression(1), new PrimitiveLiteralExpression(2));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), false);
});
it('handles 1 <= 1', function () {
const expression = new BinaryExpression('<=', new PrimitiveLiteralExpression(1), new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), true);
});
it('handles 2 <= 1', function () {
const expression = new BinaryExpression('<=', new PrimitiveLiteralExpression(2), new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), false);
});
it('handles 1 <= 2', function () {
const expression = new BinaryExpression('<=', new PrimitiveLiteralExpression(1), new PrimitiveLiteralExpression(2));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), true);
});
it('handles undefined ?? 1', function () {
const expression = new BinaryExpression('??', PrimitiveLiteralExpression.$undefined, new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), 1);
});
it('handles null ?? 1', function () {
const expression = new BinaryExpression('??', PrimitiveLiteralExpression.$null, new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), 1);
});
it('handles false ?? 1', function () {
const expression = new BinaryExpression('??', PrimitiveLiteralExpression.$false, new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), false);
});
it('handles 0 ?? 1', function () {
const expression = new BinaryExpression('??', new PrimitiveLiteralExpression(0), new PrimitiveLiteralExpression(1));
const scope = createScopeForTest({ });
assert.strictEqual(astEvaluate(expression, scope, null, null), 0);
});
class TestData {
public constructor(
public expr: BinaryExpression,
public expected: boolean,
public scope: Scope = createScopeForTest(),
) { }
public toString() { return `${this.expr}`; }
}
describe('performs \'in\'', function () {
function* getTestData() {
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), new ObjectLiteralExpression(['foo'], [$null])), true);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), new ObjectLiteralExpression(['bar'], [$null])), false);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression(1), new ObjectLiteralExpression(['1'], [$null])), true);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('1'), new ObjectLiteralExpression(['1'], [$null])), true);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), $null), false);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), $undefined), false);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), $true), false);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), $parent), false);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('bar'), $parent), false);
const scope1 = createScopeForTest({ foo: { bar: null }, bar: null });
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), $this), true, scope1);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('bar'), $this), true, scope1);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('foo'), new AccessScopeExpression('foo', 0)), false, scope1);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('bar'), new AccessScopeExpression('bar', 0)), false, scope1);
yield new TestData(new BinaryExpression('in', new PrimitiveLiteralExpression('bar'), new AccessScopeExpression('foo', 0)), true, scope1);
}
for (const item of getTestData()) {
it(item.toString(), function () {
assert.strictEqual(astEvaluate(item.expr, item.scope, null, null), item.expected, `astEvaluate(expr, scope, null, null)`);
});
}
});
describe('performs \'instanceof\'', function () {
class Foo { }
class Bar extends Foo { }
function* getTestData() {
for (const scope of [
createScopeForTest({ foo: new Foo(), bar: new Bar() }),
]) {
yield new TestData(
new BinaryExpression(
'instanceof',
new AccessScopeExpression('foo', 0),
new AccessMemberExpression(new AccessScopeExpression('foo', 0), 'constructor')
),
true,
scope,
);
yield new TestData(
new BinaryExpression(
'instanceof',
new AccessScopeExpression('foo', 0),
new AccessMemberExpression(new AccessScopeExpression('bar', 0), 'constructor')
),
false,
scope,
);
yield new TestData(
new BinaryExpression(
'instanceof',
new AccessScopeExpression('bar', 0),
new AccessMemberExpression(new AccessScopeExpression('bar', 0), 'constructor')
),
true,
scope,
);
yield new TestData(
new BinaryExpression(
'instanceof',
new AccessScopeExpression('bar', 0),
new AccessMemberExpression(new AccessScopeExpression('foo', 0), 'constructor')
),
true,
scope,
);
yield new TestData(
new BinaryExpression(
'instanceof',
new PrimitiveLiteralExpression('foo'),
new AccessMemberExpression(new AccessScopeExpression('foo', 0), 'constructor')
),
false,
scope,
);
}
yield new TestData(new BinaryExpression('instanceof', new AccessScopeExpression('foo', 0), new AccessScopeExpression('foo', 0)), false);
yield new TestData(new BinaryExpression('instanceof', new AccessScopeExpression('foo', 0), $null), false);
yield new TestData(new BinaryExpression('instanceof', new AccessScopeExpression('foo', 0), $undefined), false);
yield new TestData(new BinaryExpression('instanceof', $null, new AccessScopeExpression('foo', 0)), false);
yield new TestData(new BinaryExpression('instanceof', $undefined, new AccessScopeExpression('foo', 0)), false);
}
for (const item of getTestData()) {
it(item.toString(), function () {
assert.strictEqual(astEvaluate(item.expr, item.scope, null, null), item.expected, `astEvaluate(expr, scope, null, null)`);
});
}
});
});
describe('CallMemberExpression', function () {
it(`evaluates`, function () {
const expression = new CallMemberExpression(new AccessScopeExpression('foo', 0), 'bar', []);
let callCount = 0;
const bindingContext = {
foo: {
bar: () => {
++callCount;
return 'baz';
}
}
};
const scope = createScopeForTest(bindingContext);
assert.strictEqual(astEvaluate(expression, scope, null, null), 'baz', `astEvaluate(expression, scope, null, null)`);
assert.strictEqual(callCount, 1, 'callCount');
});
it(`evaluate handles null/undefined member`, function () {
const expression = new CallMemberExpression(new AccessScopeExpression('foo', 0), 'bar', []);
const s1: Scope = createScopeForTest({ foo: {} });
const s2: Scope = createScopeForTest({ foo: { bar: undefined } });
const s3: Scope = createScopeForTest({ foo: { bar: null } });
assert.strictEqual(astEvaluate(expression, s1, null, null), undefined, `astEvaluate(expression, createScopeForTest({ foo: {} }), null, null)`);
assert.strictEqual(astEvaluate(expression, s2, null, null), undefined, `astEvaluate(expression, createScopeForTest({ foo: { bar: undefined } }), null, null)`);
assert.strictEqual(astEvaluate(expression, s3, null, null), undefined, `astEvaluate(expression, createScopeForTest({ foo: { bar: null } }), null, null)`);
});
it(`evaluate throws when mustEvaluate and member is null or undefined`, function () {
const expression = new CallMemberExpression(new AccessScopeExpression('foo', 0), 'bar', []);
const s1 = createScopeForTest({});
const s2 = createScopeForTest({ foo: {} });
const s3 = createScopeForTest({ foo: { bar: undefined } });
const s4 = createScopeForTest({ foo: { bar: null } });
assert.throws(() => astEvaluate(expression, s1, { strictFnCall: true }, null));
assert.throws(() => astEvaluate(expression, s2, { strictFnCall: true }, null));
assert.throws(() => astEvaluate(expression, s3, { strictFnCall: true }, null));
assert.throws(() => astEvaluate(expression, s4, { strictFnCall: true }, null));
});
});
describe('CallScopeExpression', function () {
const foo: CallScopeExpression = new CallScopeExpression('foo', [], 0);
const hello: CallScopeExpression = new CallScopeExpression('hello', [new AccessScopeExpression('arg', 0)], 0);
function getScopes(initialScope: Scope) {
return [initialScope];
}
it(`evaluates defined property on bindingContext`, function () {
const [scope] = getScopes(createScopeForTest({ foo: () => 'bar', hello: arg => arg, arg: 'world' }));
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
assert.strictEqual(astEvaluate(hello, scope, null, null), 'world', `astEvaluate(hello, scope, null, null)`);
});
it(`evaluates defined property on overrideContext`, function () {
const s = createScopeForTest({ abc: () => 'xyz' });
s.overrideContext.foo = () => 'bar';
s.overrideContext.hello = arg => arg;
s.overrideContext.arg = 'world';
const [scope] = getScopes(s);
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
assert.strictEqual(astEvaluate(hello, scope, null, null), 'world', `astEvaluate(hello, scope, null, null)`);
});
it(`evaluate with connects defined property on bindingContext`, function () {
const [scope] = getScopes(createScopeForTest({ foo: () => 'bar' }));
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 0, `binding.calls.filter(c => c[0] === 'observe').length`);
astEvaluate(hello, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.bindingContext, 'arg'], 'binding.calls[0]');
});
it(`connects defined property on overrideContext`, function () {
const s1 = createScopeForTest({ abc: 'xyz' });
s1.overrideContext.foo = () => 'bar';
s1.overrideContext.hello = arg => arg;
s1.overrideContext.arg = 'world';
const [scope] = getScopes(s1);
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 0, `binding.calls.filter(c => c[0] === 'observe').length`);
astEvaluate(hello, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.overrideContext, 'arg'], 'binding.calls[0]');
});
it(`connects undefined property on bindingContext`, function () {
const [scope] = getScopes(createScopeForTest({ abc: 'xyz' }));
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 0, `binding.calls.filter(c => c[0] === 'observe').length`);
astEvaluate(hello, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.bindingContext, 'arg'], 'binding.calls[0]');
});
it(`evaluates defined property on first ancestor bindingContext`, function () {
const [scope] = getScopes(createScopeForTest({ abc: 'xyz' }, { foo: () => 'bar', hello: arg => arg, arg: 'world' }));
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
assert.strictEqual(astEvaluate(hello, scope, null, null), 'world', `astEvaluate(hello, scope, null, null)`);
});
it(`evaluates defined property on first ancestor overrideContext`, function () {
const s1 = createScopeForTest({ abc: 'xyz' }, { def: 'rsw' });
s1.parent.overrideContext.foo = () => 'bar';
s1.parent.overrideContext.hello = arg => arg;
s1.parent.overrideContext.arg = 'world';
const [scope] = getScopes(s1);
assert.strictEqual(astEvaluate(foo, scope, null, null), 'bar', `astEvaluate(foo, scope, null, null)`);
assert.strictEqual(astEvaluate(hello, scope, null, null), 'world', `astEvaluate(hello, scope, null, null)`);
});
it(`connects defined property on first ancestor bindingContext`, function () {
const [scope] = getScopes(createScopeForTest({ abc: 'xyz' }, { foo: () => 'bar', hello: arg => arg, arg: 'world' }));
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 0, `binding.calls.filter(c => c[0] === 'observe').length`);
astEvaluate(hello, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.bindingContext, 'arg'], 'binding.calls[0]');
});
it(`connects defined property on first ancestor overrideContext`, function () {
const s1 = createScopeForTest({ abc: 'xyz' }, { def: 'rsw' });
s1.parent.overrideContext.foo = () => 'bar';
s1.parent.overrideContext.hello = arg => arg;
s1.parent.overrideContext.arg = 'world';
const [scope] = getScopes(s1);
const binding = new MockBinding();
astEvaluate(foo, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.filter(c => c[0] === 'observe').length, 0, `binding.calls.filter(c => c[0] === 'observe').length`);
astEvaluate(hello, scope, dummyLocator, binding);
assert.strictEqual(binding.calls.length, 1, 'binding.calls.length');
assert.deepStrictEqual(binding.calls[0], ['observe', scope.parent.overrideContext, 'arg'], 'binding.calls[0]');
});
});
class Test {
public value: string;
public constructor() {
this.value = 'foo';
}
public makeString = (cooked: string[], a: any, b: any): string => {
return `${cooked[0]}${a}${cooked[1]}${b}${cooked[2]}${this.value}`;
};
}
describe('LiteralTemplate', function () {
class TestData {
public constructor(
public readonly expr: TemplateExpression | TaggedTemplateExpression,
public readonly expected: string,
public readonly ctx: any = {},
public readonly hsCtx: any = null,
public readonly only: boolean = false
) { }
public get scope() { return createScopeForTest(this.ctx); }
}
function* getTestData() {
yield new TestData($tpl, '');
yield new TestData(new TemplateExpression(['foo']), 'foo');
yield new TestData(new TemplateExpression(['foo', 'baz'], [new PrimitiveLiteralExpression('bar')]), 'foobarbaz');
yield new TestData(
new TemplateExpression(
['a', 'c', 'e', 'g'],
[new PrimitiveLiteralExpression('b'), new PrimitiveLiteralExpression('d'), new PrimitiveLiteralExpression('f')]
),
'abcdefg',
);
yield new TestData(
new TemplateExpression(['a', 'c', 'e'], [new AccessScopeExpression('b', 0), new AccessScopeExpression('d', 0)]),
'a1c2e',
{ b: 1, d: 2 }
);
yield new TestData(
new TaggedTemplateExpression(
[''],
[],
new AccessScopeExpression('foo', 0)
),
'foo',
{ foo: () => 'foo' }
);
yield new TestData(
new TaggedTemplateExpression(
['foo'],
['bar'],
new AccessScopeExpression('baz', 0)
),
'foobar',
{ baz: cooked => `${cooked[0]}${cooked.raw[0]}` }
);
yield new TestData(
new TaggedTemplateExpression(
['1', '2'],
[],
new AccessScopeExpression('makeString', 0),
[new PrimitiveLiteralExpression('foo')]
),
'1foo2',
{ makeString: (cooked, foo) => `${cooked[0]}${foo}${cooked[1]}` }
);
yield new TestData(
new TaggedTemplateExpression(
['1', '2'],
[],
new AccessScopeExpression('makeString', 0),
[new AccessScopeExpression('foo', 0)]
),
'1bar2',
{ foo: 'bar', makeString: (cooked, foo) => `${cooked[0]}${foo}${cooked[1]}` }
);
yield new TestData(
new TaggedTemplateExpression(
['1', '2', '3'],
[],
new AccessScopeExpression('makeString', 0),
[new AccessScopeExpression('foo', 0), new AccessScopeExpression('bar', 0)]
),
'bazqux',
{ foo: 'baz', bar: 'qux', makeString: (cooked, foo, bar) => `${foo}${bar}` }
);
yield new TestData(
new TaggedTemplateExpression(
['1', '2', '3'],
[],
new AccessMemberExpression(new AccessScopeExpression('test', 0), 'makeString'),
[new AccessScopeExpression('foo', 0), new AccessScopeExpression('bar', 0)]
),
'1baz2qux3foo',
{ foo: 'baz', bar: 'qux', test: new Test() }
);
yield new TestData(
new TaggedTemplateExpression(
['1', '2', '3'],
[],
new AccessKeyedExpression(new AccessScopeExpression('test', 0), new PrimitiveLiteralExpression('makeString')),
[new AccessScopeExpression('foo', 0), new AccessScopeExpression('bar', 0)]
),
'1baz2qux3foo',
{ foo: 'baz', bar: 'qux', test: new Test() }
);
}
for (const item of getTestData()) {
const $it = item.only ? it.only : it;
$it(`${item.expr} evaluates ${item.expected}`, function () {
assert.strictEqual(astEvaluate(item.expr, item.scope, null, null), item.expected, `astEvaluate(item.expr, scope, null, null)`);
});
}
});
describe('UnaryExpression', function () {
describe('performs \'typeof\'', function () {
const tests: { expr: UnaryExpression; expected: string }[] = [
{ expr: new UnaryExpression('typeof', new PrimitiveLiteralExpression('foo')), expected: 'string' },
{ expr: new UnaryExpression('typeof', new PrimitiveLiteralExpression(1)), expected: 'number' },
{ expr: new UnaryExpression('typeof', $null), expected: 'object' },
{ expr: new UnaryExpression('typeof', $undefined), expected: 'undefined' },
{ expr: new UnaryExpression('typeof', $true), expected: 'boolean' },
{ expr: new UnaryExpression('typeof', $false), expected: 'boolean' },
{ expr: new UnaryExpression('typeof', $arr), expected: 'object' },
{ expr: new UnaryExpression('typeof', $obj), expected: 'object' },
{ expr: new UnaryExpression('typeof', $this), expected: 'object' },
{ expr: new UnaryExpression('typeof', $parent), expected: 'undefined' },
{ expr: new UnaryExpression('typeof', new AccessScopeExpression('foo', 0)), expected: 'string' }
];
const scope: Scope = createScopeForTest({});
for (const { expr, expected } of tests) {
it(expr.toString(), function () {
assert.strictEqual(astEvaluate(expr, scope, null, null), expected, `astEvaluate(expr, scope, null)`);
});
}
});
describe('performs \'void\'', function () {
const tests: { expr: UnaryExpression }[] = [
{ expr: new UnaryExpression('void', new PrimitiveLiteralExpression('foo')) },
{ expr: new UnaryExpression('void', new PrimitiveLiteralExpression(1)) },
{ expr: new UnaryExpression('void', $null) },
{ expr: new UnaryExpression('void', $undefined) },
{ expr: new UnaryExpression('void', $true) },
{ expr: new UnaryExpression('void', $false) },
{ expr: new UnaryExpression('void', $arr) },
{ expr: new UnaryExpression('void', $obj) },
{ expr: new UnaryExpression('void', $this) },
{ expr: new UnaryExpression('void', $parent) },
{ expr: new UnaryExpression('void', new AccessScopeExpression('foo', 0)) }
];
let scope: Scope = createScopeForTest({});
for (const { expr } of tests) {
it(expr.toString(), function () {
assert.strictEqual(astEvaluate(expr, scope, null, null), undefined, `astEvaluate(expr, scope, null)`);
});
}
it('void foo()', function () {
let fooCalled = false;
const foo = () => (fooCalled = true);
scope = createScopeForTest({ foo });
const expr = new UnaryExpression('void', new CallScopeExpression('foo', [], 0));
assert.strictEqual(astEvaluate(expr, scope, null, null), undefined, `astEvaluate(expr, scope, null)`);
assert.strictEqual(fooCalled, true, `fooCalled`);
});
});
});
describe('DestructuringAssignmentExpression', function () {
describe('DestructuringAssignmentSingleExpression', function () {
it('{a} = {a:42}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
void 0,
), Scope.create(bc), null, { a: 42 });
assert.strictEqual(bc.a, 42);
});
it('{1:a} = {1:"42"}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, '1'),
void 0,
), Scope.create(bc), null, { 1: '42' });
assert.strictEqual(bc.a, '42');
});
it('{x:a} = {x:"42"}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'x'),
void 0,
), Scope.create(bc), null, { x: '42' });
assert.strictEqual(bc.a, '42');
});
it('{a=42} = {b:404}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
new PrimitiveLiteralExpression(42),
), Scope.create(bc), null, { b: 404 });
assert.strictEqual(bc.a, 42);
});
it('{1:a=42} = {2:"404"}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, '1'),
new PrimitiveLiteralExpression(42),
), Scope.create(bc), null, { 2: "404" });
assert.strictEqual(bc.a, 42);
});
it('{x:a=42} = {b:404}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'x'),
new PrimitiveLiteralExpression(42),
), Scope.create(bc), null, { b: 404 });
assert.strictEqual(bc.a, 42);
});
it('{a=404} = {a:42}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
new PrimitiveLiteralExpression(404),
), Scope.create(bc), null, { a: 42 });
assert.strictEqual(bc.a, 42);
});
it('{1:a=404} = {1:"42"}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, '1'),
new PrimitiveLiteralExpression(404),
), Scope.create(bc), null, { 1: '42' });
assert.strictEqual(bc.a, '42');
});
it('{x:a=404} = {x:"42"}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'x'),
new PrimitiveLiteralExpression(404),
), Scope.create(bc), null, { x: '42' });
assert.strictEqual(bc.a, '42');
});
it('[a] = [42]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
void 0,
), Scope.create(bc), null, [42]);
assert.strictEqual(bc.a, 42);
});
it('[a=42] = []', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
new PrimitiveLiteralExpression(42),
), Scope.create(bc), null, []);
assert.strictEqual(bc.a, 42);
});
it('[,a=42] = [404]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
new PrimitiveLiteralExpression(42),
), Scope.create(bc), null, [404]);
assert.strictEqual(bc.a, 42);
});
it('{a=vm_prop} = {x:404}', function () {
const ps = Scope.create({ prop: 42 });
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
new AccessScopeExpression('prop', 0),
), Scope.fromParent(ps, bc), null, { x: 404 });
assert.strictEqual(bc.a, 42);
});
it('[,a=vm_prop] = [404]', function () {
const ps = Scope.create({ prop: 42 });
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
new AccessScopeExpression('prop', 0),
), Scope.fromParent(ps, bc), null, [404]);
assert.strictEqual(bc.a, 42);
});
it('{a=$parent.vm_prop} = {x:404}', function () {
const ps = Scope.create({ prop: 42 });
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
new AccessScopeExpression('prop', 2),
), Scope.fromParent(Scope.fromParent(ps, Object.create(null)), bc), null, { x: 404 });
assert.strictEqual(bc.a, 42);
});
it('[,a=$parent.vm_prop] = [404]', function () {
const ps = Scope.create({ prop: 42 });
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
new AccessScopeExpression('prop', 2),
), Scope.fromParent(Scope.fromParent(ps, Object.create(null)), bc), null, [404]);
assert.strictEqual(bc.a, 42);
});
});
describe('DestructuringAssignmentRestExpression', function () {
it('{...rest} = {a:1, b:2}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
[],
), Scope.create(bc), null, { a: 1, b: 2 });
assert.deepStrictEqual(bc, { rest: { a: 1, b: 2 } });
});
it('{a, ...rest} = {a:1, b:2}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
['a'],
), Scope.create(bc), null, { a: 1, b: 2 });
assert.deepStrictEqual(bc, { rest: { b: 2 } });
});
it('{a, b, ...rest} = {a:1, b:2, c:3}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
['a', 'b'],
), Scope.create(bc), null, { a: 1, b: 2, c: 3 });
assert.deepStrictEqual(bc, { rest: { c: 3 } });
});
it('{a, b, ...rest} = {a:1, b:2}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
['a', 'b'],
), Scope.create(bc), null, { a: 1, b: 2 });
assert.deepStrictEqual(bc, { rest: {} });
});
it('[...rest] = [1, 2]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
0,
), Scope.create(bc), null, [1, 2]);
assert.deepStrictEqual(bc, { rest: [1, 2] });
});
it('[,...rest] = [1, 2]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
1,
), Scope.create(bc), null, [1, 2]);
assert.deepStrictEqual(bc, { rest: [2] });
});
it('[,,...rest] = [1, 2]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
3,
), Scope.create(bc), null, [1, 2]);
assert.deepStrictEqual(bc, { rest: [] });
});
});
describe('DestructuringAssignmentExpression', function () {
it('{a} = {a: 1, b:2}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
void 0
)
],
void 0,
void 0
), Scope.create(bc), null, { a: 1, b: 2 });
assert.deepStrictEqual(bc, { a: 1 });
});
it('{a, b} = {a: 1, b:2}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
void 0
),
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'b'),
new AccessMemberExpression($this, 'b'),
void 0
),
],
void 0,
void 0
), Scope.create(bc), null, { a: 1, b: 2 });
assert.deepStrictEqual(bc, { a: 1, b: 2 });
});
it('{...rest} = {a: 1, b:2}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
[]
),
],
void 0,
void 0
), Scope.create(bc), null, { a: 1, b: 2 });
assert.deepStrictEqual(bc.rest, { a: 1, b: 2 });
});
it('[a] = [1, 2]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
void 0
),
],
void 0,
void 0
), Scope.create(bc), null, [1, 2]);
assert.deepStrictEqual(bc, { a: 1 });
});
it('[a, b] = [1, 2]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
void 0
),
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'b'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
void 0
),
],
void 0,
void 0
), Scope.create(bc), null, [1, 2]);
assert.deepStrictEqual(bc, { a: 1, b: 2 });
});
it('[...rest] = [1, 2]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentRestExpression(
new AccessMemberExpression($this, 'rest'),
0,
),
],
void 0,
void 0
), Scope.create(bc), null, [1, 2]);
assert.deepStrictEqual(bc, { rest: [1, 2] });
});
it('{prop1, prop2:{prop21}} = {prop1: "foo", prop2: {prop21: 123}}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop1'),
new AccessMemberExpression($this, 'prop1'),
void 0
),
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop21'),
new AccessMemberExpression($this, 'prop21'),
void 0
),
],
new AccessMemberExpression($this, 'prop2'),
void 0,
),
],
void 0,
void 0,
), Scope.create(bc), null, { prop1: 'foo', prop2: { prop21: 123 } });
assert.deepStrictEqual(bc, { prop1: 'foo', prop21: 123 });
});
it('{prop1, prop2:{prop21:{prop212:newProp212}, prop22}} = {prop1: "foo", prop2: {prop21: {prop211: 123, prop212: 456}, prop22: "bar" }}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop1'),
new AccessMemberExpression($this, 'prop1'),
void 0
),
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'newProp212'),
new AccessMemberExpression($this, 'prop212'),
void 0
),
],
new AccessMemberExpression($this, 'prop21'),
void 0,
),
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop22'),
new AccessMemberExpression($this, 'prop22'),
void 0
),
],
new AccessMemberExpression($this, 'prop2'),
void 0,
),
],
void 0,
void 0,
), Scope.create(bc), null, { prop1: 'foo', prop2: { prop21: { prop211: 123, prop212: 456 }, prop22: 'bar' } });
assert.deepStrictEqual(bc, { prop1: 'foo', newProp212: 456, prop22: 'bar' });
});
it('{prop1,coll:[,{p2:item2p2}]} = {prop1:"foo",coll:[{p1:1,p2:2},{p1:3,p2:4}]}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop1'),
new AccessMemberExpression($this, 'prop1'),
void 0
),
new DestructuringAssignmentExpression(
'ArrayDestructuring',
[
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'item2p2'),
new AccessMemberExpression($this, 'p2'),
void 0,
)
],
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
void 0,
)
],
new AccessMemberExpression($this, 'coll'),
void 0,
),
],
void 0,
void 0,
), Scope.create(bc), null, { prop1: 'foo', coll: [{ p1: 1, p2: 2 }, { p1: 3, p2: 4 }] });
assert.deepStrictEqual(bc, { prop1: 'foo', item2p2: 4 });
});
it('{prop1,coll:[,{p:[item21]}]} = {prop1:"foo",coll:[{p:[1,2]},{p:[3,4]}]}', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop1'),
new AccessMemberExpression($this, 'prop1'),
void 0
),
new DestructuringAssignmentExpression(
'ArrayDestructuring',
[
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentExpression(
'ArrayDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'item21'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
void 0
),
],
new AccessMemberExpression($this,'p'),
void 0,
),
],
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
void 0,
)
],
new AccessMemberExpression($this, 'coll'),
void 0,
),
],
void 0,
void 0,
), Scope.create(bc), null, { prop1: "foo", coll: [{ p: [1, 2] }, { p: [3, 4] }] });
assert.deepStrictEqual(bc, { prop1: 'foo', item21: 3 });
});
it('[k, {prop1, prop2:{prop21}}] = ["key",{prop1: "foo", prop2: {prop21: 123}}]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ArrayDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'k'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
void 0
),
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop1'),
new AccessMemberExpression($this, 'prop1'),
void 0
),
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'prop21'),
new AccessMemberExpression($this, 'prop21'),
void 0
),
],
new AccessMemberExpression($this, 'prop2'),
void 0,
),
],
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
void 0,
)
],
void 0,
void 0,
), Scope.create(bc), null, ['key', { prop1: 'foo', prop2: { prop21: 123 } }]);
assert.deepStrictEqual(bc, { k: 'key', prop1: 'foo', prop21: 123 });
});
it('[k, [,item2]] = ["key",[1,2]]', function () {
const bc: Record<string, any> = {};
astAssign(new DestructuringAssignmentExpression(
'ArrayDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'k'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(0)),
void 0
),
new DestructuringAssignmentExpression(
'ArrayDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'item2'),
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
void 0
)
],
new AccessKeyedExpression($this, new PrimitiveLiteralExpression(1)),
void 0,
)
],
void 0,
void 0,
), Scope.create(bc), null, ['key', [1,2]]);
assert.deepStrictEqual(bc, { k: 'key', item2: 2 });
});
it('{a,b:{c}={c:42}} = {a:42}', function () {
const bc: Record<string, any> = {};
const expr = new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'a'),
new AccessMemberExpression($this, 'a'),
void 0
),
new DestructuringAssignmentExpression(
'ObjectDestructuring',
[
new DestructuringAssignmentSingleExpression(
new AccessMemberExpression($this, 'c'),
new AccessMemberExpression($this, 'c'),
void 0
)
],
new AccessMemberExpression($this, 'b'),
new ObjectLiteralExpression(['c'], [new PrimitiveLiteralExpression(42)])
)
],
void 0,
void 0
);
astAssign(expr, Scope.create(bc), null, {a:42});
assert.deepStrictEqual(bc, { a:42, c:42});
});
});
});
describe('arrow function unparsing', function () {
it('unparses arrow fn', function () {
assert.strictEqual(
Unparser.unparse(new ArrowFunction([new BindingIdentifier('a')], new AccessScopeExpression('a'))),
'(a) => a'
);
});
it('unparses arrow fn with single rest parameter', function () {
assert.strictEqual(
Unparser.unparse(new ArrowFunction([new BindingIdentifier('a')], new AccessScopeExpression('a'), true)),
'(...a) => a'
);
});
it('unparses arrow fn with 2 params', function () {
assert.strictEqual(
Unparser.unparse(new ArrowFunction([new BindingIdentifier('a'), new BindingIdentifier('b')], new AccessScopeExpression('a'))),
'(a, b) => a'
);
});
it('unparses arrow fn with 2 params with rest', function () {
assert.strictEqual(
Unparser.unparse(new ArrowFunction([new BindingIdentifier('a'), new BindingIdentifier('b')], new AccessScopeExpression('a'), true)),
'(a, ...b) => a'
);
});
});
});