blob: 4c1b6d458eef4511061fc64eb18508115afb3b46 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// ES6 for/for-in/for-of loops per iteration loop variable bindings tests
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
var tests = [
{
name: "[slot array] for-in and for-of loops have per iteration bindings for let and const variables",
body: function () {
var a = [];
var i = 0;
for (let x in { a: 1, b: 2, c: 3 }) {
a[i++] = function () { return x; };
}
for (const x in { a: 1, b: 2, c: 3 }) {
a[i++] = function () { return x; };
}
for (let x of [ 1, 2, 3 ]) {
a[i++] = function () { return x; };
}
for (const x of [ 1, 2, 3 ]) {
a[i++] = function () { return x; };
}
assert.areEqual('a', a[0](), "first for-let-in function captures x when it is equal to 'a'");
assert.areEqual('b', a[1](), "second for-let-in function captures x when it is equal to 'b'");
assert.areEqual('c', a[2](), "third for-let-in function captures x when it is equal to 'c'");
assert.areEqual('a', a[3](), "first for-const-in function captures x when it is equal to 'a'");
assert.areEqual('b', a[4](), "second for-const-in function captures x when it is equal to 'b'");
assert.areEqual('c', a[5](), "third for-const-in function captures x when it is equal to 'c'");
assert.areEqual(1, a[6](), "first for-let-of function captures x when it is equal to 1");
assert.areEqual(2, a[7](), "second for-let-of function captures x when it is equal to 2");
assert.areEqual(3, a[8](), "third for-let-of function captures x when it is equal to 3");
assert.areEqual(1, a[9](), "first for-const-of function captures x when it is equal to 1");
assert.areEqual(2, a[10](), "second for-const-of function captures x when it is equal to 2");
assert.areEqual(3, a[11](), "third for-const-of function captures x when it is equal to 3");
}
},
{
name: "[activation object] for-in and for-of loops have per iteration bindings for let and const variables",
body: function () {
var a = [];
var i = 0;
for (let x in { a: 1, b: 2, c: 3 }) {
a[i++] = function () { return eval("x"); };
}
for (const x in { a: 1, b: 2, c: 3 }) {
a[i++] = function () { return eval("x"); };
}
for (let x of [ 1, 2, 3 ]) {
a[i++] = function () { return eval("x"); };
}
for (const x of [ 1, 2, 3 ]) {
a[i++] = function () { return eval("x"); };
}
assert.areEqual('a', a[0](), "first for-let-in function captures x when it is equal to 'a'");
assert.areEqual('b', a[1](), "second for-let-in function captures x when it is equal to 'b'");
assert.areEqual('c', a[2](), "third for-let-in function captures x when it is equal to 'c'");
assert.areEqual('a', a[3](), "first for-const-in function captures x when it is equal to 'a'");
assert.areEqual('b', a[4](), "second for-const-in function captures x when it is equal to 'b'");
assert.areEqual('c', a[5](), "third for-const-in function captures x when it is equal to 'c'");
assert.areEqual(1, a[6](), "first for-let-of function captures x when it is equal to 1");
assert.areEqual(2, a[7](), "second for-let-of function captures x when it is equal to 2");
assert.areEqual(3, a[8](), "third for-let-of function captures x when it is equal to 3");
assert.areEqual(1, a[9](), "first for-const-of function captures x when it is equal to 1");
assert.areEqual(2, a[10](), "second for-const-of function captures x when it is equal to 2");
assert.areEqual(3, a[11](), "third for-const-of function captures x when it is equal to 3");
}
},
{
name: "const variables in for-in and for-of loops cannot be assigned",
body: function () {
assert.throws(function () { for (const x in { a: 1 }) { x = 1; } }, TypeError, "assignment to const known at parse time in for-in loop", "Assignment to const");
assert.throws(function () { for (const x of [ 0 ]) { x = 1; } }, TypeError, "assignment to const known at parse time in for-of loop", "Assignment to const");
assert.throws(function () { eval("for (const x in { a: 1 }) { x = 1; }"); }, TypeError, "assignment to const known at eval parse time in for-in loop", "Assignment to const");
assert.throws(function () { for (const x in { a: 1 }) { eval("x = 1;"); } }, TypeError, "assignment to const only known at run time in for-in loop", "Assignment to const");
assert.throws(function () { eval("for (const x of [ 0 ]) { x = 1; }"); }, TypeError, "assignment to const known at eval parse time in for-of loop", "Assignment to const");
assert.throws(function () { for (const x of [ 0 ]) { eval("x = 1;"); } }, TypeError, "assignment to const only known at run time in for-of loop", "Assignment to const");
}
},
{
name: "Referring to a let or const loop variable in the collection expression of a for-in or for-of loop is a use before declaration",
body: function () {
assert.throws(function () { for (let x in { a: x }) { } }, ReferenceError, "for-let-in register use before declaration", "Use before declaration");
assert.throws(function () { for (const x in { a: x }) { } }, ReferenceError, "for-const-in register use before declaration", "Use before declaration");
assert.throws(function () { for (let x of [ x ]) { } }, ReferenceError, "for-let-of register use before declaration", "Use before declaration");
assert.throws(function () { for (const x of [ x ]) { } }, ReferenceError, "for-const-of register use before declaration", "Use before declaration");
assert.throws(function () { for (let x in { a: (() => x)() }) { } }, ReferenceError, "for-let-in slot array use before declaration", "Use before declaration");
assert.throws(function () { for (const x in { a: (() => x)() }) { } }, ReferenceError, "for-const-in slot array use before declaration", "Use before declaration");
assert.throws(function () { for (let x of [ (() => x)() ]) { } }, ReferenceError, "for-let-of slot array use before declaration", "Use before declaration");
assert.throws(function () { for (const x of [ (() => x)() ]) { } }, ReferenceError, "for-const-of slot array use before declaration", "Use before declaration");
assert.throws(function () { for (let x in { a: eval("x") }) { } }, ReferenceError, "for-let-in activation object use before declaration", "Use before declaration");
assert.throws(function () { for (const x in { a: eval("x") }) { } }, ReferenceError, "for-const-in activation object use before declaration", "Use before declaration");
assert.throws(function () { for (let x of [ eval("x") ]) { } }, ReferenceError, "for-let-of activation object use before declaration", "Use before declaration");
assert.throws(function () { for (const x of [ eval("x") ]) { } }, ReferenceError, "for-const-of activation object use before declaration", "Use before declaration");
var rx0 = null, rx1 = null;
var ry0 = null, ry1 = null;
function registerVars() {
for (var x in { a: rx0 = x }) {
rx1 = x;
}
for (var y of [ ry0 = y ]) {
ry1 = y;
}
}
registerVars();
assert.areEqual(undefined, rx0, "register var declaration in for-in loop can be referenced before initialization");
assert.areEqual('a', rx1, "sanity check that the for-in loop runs as expected");
assert.areEqual(undefined, ry0, "register var declaration in for-of loop can be referenced before initialization");
assert.areEqual(undefined, ry1, "sanity check that the for-of loop runs as expected");
var sax0 = null, sax1 = null;
var say0 = null, say1 = null;
function slotArrayVars() {
for (var x in { a: sax0 = x }) {
sax1 = (function () { return x; })();
}
for (var y of [ say0 = y ]) {
say1 = (function () { return y; })();
}
}
slotArrayVars();
assert.areEqual(undefined, sax0, "slot array var declaration in for-in loop can be referenced before initialization");
assert.areEqual('a', sax1, "sanity check that the for-in loop runs as expected");
assert.areEqual(undefined, say0, "slot array var declaration in for-of loop can be referenced before initialization");
assert.areEqual(undefined, say1, "sanity check that the for-of loop runs as expected");
var aox0 = null, aox1 = null;
var aoy0 = null, aoy1 = null;
function activationObjectVars() {
for (var x in { a: aox0 = x }) {
aox1 = eval("x");
}
for (var y of [ aoy0 = y ]) {
aoy1 = eval("y");
}
}
activationObjectVars();
assert.areEqual(undefined, aox0, "slot array var declaration in for-in loop can be referenced before initialization");
assert.areEqual('a', aox1, "sanity check that the for-in loop runs as expected");
assert.areEqual(undefined, aoy0, "slot array var declaration in for-of loop can be referenced before initialization");
assert.areEqual(undefined, aoy1, "sanity check that the for-of loop runs as expected");
}
},
{
name: "Capturing a let or const loop variable in the collection expression of a for-in or for-of loop still leads to a use before declaration",
body: function () {
var a = [], b = [];
var i = 0, j = 0;
for (let x in { a: a[i++] = () => x }) { b[j++] = () => x; }
for (const x in { a: a[i++] = () => x }) { b[j++] = () => x; }
for (let x of [ a[i++] = () => x ]) { b[j++] = () => x; }
for (const x of [ a[i++] = () => x ]) { b[j++] = () => x; }
for (let x in { a: a[i++] = () => eval("x") }) { b[j++] = () => eval("x"); }
for (const x in { a: a[i++] = () => eval("x") }) { b[j++] = () => eval("x"); }
for (let x of [ a[i++] = () => eval("x") ]) { b[j++] = () => eval("x"); }
for (const x of [ a[i++] = () => eval("x") ]) { b[j++] = () => eval("x"); }
assert.throws(a[0], ReferenceError, "for-let-in slot array capture use before declaration", "Use before declaration");
assert.throws(a[1], ReferenceError, "for-const-in slot array capture use before declaration", "Use before declaration");
assert.throws(a[2], ReferenceError, "for-let-of slot array capture use before declaration", "Use before declaration");
assert.throws(a[3], ReferenceError, "for-const-of slot array capture use before declaration", "Use before declaration");
assert.throws(a[4], ReferenceError, "for-let-in activation object capture use before declaration", "Use before declaration");
assert.throws(a[5], ReferenceError, "for-const-in activation object capture use before declaration", "Use before declaration");
assert.throws(a[6], ReferenceError, "for-let-of activation object capture use before declaration", "Use before declaration");
assert.throws(a[7], ReferenceError, "for-const-of activation object capture use before declaration", "Use before declaration");
assert.areEqual('a', b[0](), "sanity check for-let-in slot array capture body still initialized", "Use before declaration");
assert.areEqual('a', b[1](), "sanity check for-const-in slot array capture body still initialized", "Use before declaration");
assert.areEqual(a[2], b[2](), "sanity check for-let-of slot array capture body still initialized", "Use before declaration");
assert.areEqual(a[3], b[3](), "sanity check for-const-of slot array capture body still initialized", "Use before declaration");
assert.areEqual('a', b[4](), "sanity check for-let-in activation object capture body still initialized", "Use before declaration");
assert.areEqual('a', b[5](), "sanity check for-const-in activation object capture body still initialized", "Use before declaration");
assert.areEqual(a[6], b[6](), "sanity check for-let-of activation object capture body still initialized", "Use before declaration");
assert.areEqual(a[7], b[7](), "sanity check for-const-of activation object capture body still initialized", "Use before declaration");
}
},
{
name: "[slot array] for loops have per iteration bindings for let variables",
body: function () {
var a = [];
var i = 0;
for (let x = 0; x < 3; x += 1) {
a[i++] = function () { return x; };
}
assert.areEqual(0, a[0](), "first for-let function captures x when it is equal to 0");
assert.areEqual(1, a[1](), "second for-let function captures x when it is equal to 1");
assert.areEqual(2, a[2](), "third for-let function captures x when it is equal to 2");
}
},
{
name: "[activation object] for loops have per iteration bindings for let variables",
body: function () {
var a = [];
var i = 0;
for (let x = 0; x < 3; x += 1) {
a[i++] = function () { return eval("x"); };
}
assert.areEqual(0, a[0](), "first for-let function captures x when it is equal to 0");
assert.areEqual(1, a[1](), "second for-let function captures x when it is equal to 1");
assert.areEqual(2, a[2](), "third for-let function captures x when it is equal to 2");
}
},
{
name: "for loops allow const loop variables but cannot assign to them anywhere including the increment expression", // so they're kinda useless
body: function () {
assert.throws(function () { for (const x = 0; x++ < 3; ) { } }, TypeError, "assignment to const known at parse time in the test expression", "Assignment to const");
assert.throws(function () { for (const x = 0; x < 3; x += 1) { } }, TypeError, "assignment to const known at parse time in the increment expression", "Assignment to const");
assert.throws(function () { for (const x = 0; x < 3; ) { x += 1; } }, TypeError, "assignment to const known at parse time in the body", "Assignment to const");
assert.throws(function () { eval("for (const x = 0; x++ < 3; ) { }"); }, TypeError, "assignment to const known at eval parse time in the test expression", "Assignment to const");
assert.throws(function () { for (const x = 0; eval("x++") < 3; ) { } }, TypeError, "assignment to const known at run time in the test expression", "Assignment to const");
assert.throws(function () { eval("for (const x = 0; x < 3; x += 1) { }"); }, TypeError, "assignment to const known at eval parse time in the increment expression", "Assignment to const");
assert.throws(function () { for (const x = 0; x < 3; eval("x += 1")) { } }, TypeError, "assignment to const known at run time in the increment expression", "Assignment to const");
assert.throws(function () { eval("for (const x = 0; x < 3; ) { x += 1; }"); }, TypeError, "assignment to const known at eval parse time in the body", "Assignment to const");
assert.throws(function () { for (const x = 0; x < 3; ) { eval("x += 1"); } }, TypeError, "assignment to const known at run time in the body", "Assignment to const");
}
},
{
name: "for loops have separate binding for the initializer expression for let variables",
body: function () {
var a = [];
for (let x = (a[0] = () => x, 0); x < 1; x += 1) {
x += 1;
a[1] = () => x;
}
assert.areEqual(0, a[0](), "x captured in the initializer expression is a binding scoped only to the initializer expression");
assert.areEqual(1, a[1](), "x captured in the initializer expression is a binding scoped only to the initializer expression");
}
},
{
name: "for loop per iteration binding includes the test and increment expressions and ends before the increment expression",
body: function () {
var t = [], ti = 0;
var b = [], bi = 0;
var i = [], ii = 0;
var s = [], si = 0;
for (let x = 0;
// test expression
t[ti++] = () => x,
s[si++] = y => x = y,
x < 2;
// increment expression
i[ii++] = () => x,
x += 1) {
// body
b[bi++] = () => x;
}
assert.areEqual(0, t[0](), "Initially first test function captures first loop binding with value 0");
assert.areEqual(0, b[0](), "Initially first body function captures first loop binding with value 0");
assert.areEqual(1, i[0](), "Initially first increment function captures second loop binding with value 1");
s[0]('a');
assert.areEqual('a', t[0](), "Now first test function returns the new value of the first loop binding of x, 'a'");
assert.areEqual('a', b[0](), "Now first body function returns the new value of the first loop binding of x, 'a'");
assert.areEqual(1, i[0](), "But first increment function still returns 1 because it is a separate binding");
assert.areEqual(1, t[1](), "Initially second test function captures second loop binding with value 1");
assert.areEqual(1, b[1](), "Initially second body function captures second loop binding with value 1");
assert.areEqual(2, i[1](), "Initially second increment function captures third loop binding with value 2");
s[1]('b');
assert.areEqual('b', i[0](), "And now first increment function returns 'b'");
assert.areEqual('b', t[1](), "Now second test function returns the new value of the second loop binding of x, 'b'");
assert.areEqual('b', b[1](), "Now second body function returns the new value of the second loop binding of x, 'b'");
assert.areEqual(2, i[1](), "But second increment function still returns 2 because it is a separate binding");
assert.areEqual(2, t[2](), "Initially third test function captures third loop binding with value 2");
assert.areEqual(undefined, b[2], "There is no third body function because the loop terminated");
assert.areEqual(undefined, i[2], "There is no third increment function because the loop terminated");
s[2]('c');
assert.areEqual('b', i[0](), "And now second increment function returns 'c'");
assert.areEqual('b', t[1](), "Now third test function returns the new value of the third loop binding of x, 'c'");
}
},
{
name: "Destructuring adds the possibility of more than one loop variable -- quick smoke test",
body: function () {
var a = [];
var i = 0;
for (let [ x, y, z ] in { abc: 0, def: 1 }) {
a[i++] = [ () => x, () => y, () => z ];
}
for (const { x, y, z } of [ { x: 1, y: 2, z: 3 }, { x: 4, y: 5, z: 6 } ]) {
a[i++] = [ () => x, () => y, () => z ];
}
for (let [ x, y, z ] = [ 0, 1, 2 ];
x < 2;
[ x, y, z ] = [ x + 1, y + 2, z + 3 ]) {
a[i++] = [ () => x, () => y, () => z ];
}
assert.areEqual('a', a[0][0](), "for-let-in array destructuring first x gets 'a' from 'abc'");
assert.areEqual('b', a[0][1](), "for-let-in array destructuring first y gets 'b' from 'abc'");
assert.areEqual('c', a[0][2](), "for-let-in array destructuring first z gets 'c' from 'abc'");
assert.areEqual('d', a[1][0](), "for-let-in array destructuring second x gets 'd' from 'def'");
assert.areEqual('e', a[1][1](), "for-let-in array destructuring second y gets 'e' from 'def'");
assert.areEqual('f', a[1][2](), "for-let-in array destructuring second z gets 'f' from 'def'");
assert.areEqual(1, a[2][0](), "for-const-of object destructuring first x gets 1 from x");
assert.areEqual(2, a[2][1](), "for-const-of object destructuring first y gets 2 from y");
assert.areEqual(3, a[2][2](), "for-const-of object destructuring first z gets 3 from z");
assert.areEqual(4, a[3][0](), "for-const-of object destructuring second x gets 4 from x");
assert.areEqual(5, a[3][1](), "for-const-of object destructuring second y gets 5 from y");
assert.areEqual(6, a[3][2](), "for-const-of object destructuring second z gets 6 from z");
assert.areEqual(0, a[4][0](), "for-let array destructuring first x gets 0 from [ 0, 1, 2 ]");
assert.areEqual(1, a[4][1](), "for-let array destructuring first y gets 1 from [ 0, 1, 2 ]");
assert.areEqual(2, a[4][2](), "for-let array destructuring first z gets 2 from [ 0, 1, 2 ]");
assert.areEqual(1, a[5][0](), "for-let array destructuring second x gets 4 from [ x + 1, y + 2, z + 3 ]");
assert.areEqual(3, a[5][1](), "for-let array destructuring second y gets 5 from [ x + 1, y + 2, z + 3 ]");
assert.areEqual(5, a[5][2](), "for-let array destructuring second z gets 6 from [ x + 1, y + 2, z + 3 ]");
}
},
];
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });