diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index db0827c3ad0a..7f748df23bdc 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -391,3 +391,78 @@ function ArrayStaticReduceRight(list, callbackfn) { else return callFunction(ArrayReduceRight, list, callbackfn); } + +/* ES6 draft 2013-05-14 15.4.3.23. */ +function ArrayFind(predicate/*, thisArg*/) { + /* Steps 1-2. */ + var O = ToObject(this); + + /* Steps 3-5. */ + var len = ToInteger(O.length); + + /* Step 6. */ + if (arguments.length === 0) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.find'); + if (!IsCallable(predicate)) + ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate)); + + /* Step 7. */ + var T = arguments.length > 1 ? arguments[1] : undefined; + + /* Steps 8-9. */ + /* Steps a (implicit), and e. */ + /* Note: this will hang in some corner-case situations, because of IEEE-754 numbers' + * imprecision for large values. Example: + * var obj = { 18014398509481984: true, length: 18014398509481988 }; + * Array.prototype.find.call(obj, () => true); + */ + for (var k = 0; k < len; k++) { + /* Steps b and c (implicit) */ + if (k in O) { + /* Step d. */ + var kValue = O[k]; + if (callFunction(predicate, T, kValue, k, O)) + return kValue; + } + } + + /* Step 10. */ + return undefined; +} + +/* ES6 draft 2013-05-14 15.4.3.23. */ +function ArrayFindIndex(predicate/*, thisArg*/) { + /* Steps 1-2. */ + var O = ToObject(this); + + /* Steps 3-5. */ + var len = ToInteger(O.length); + + /* Step 6. */ + if (arguments.length === 0) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.find'); + if (!IsCallable(predicate)) + ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate)); + + /* Step 7. */ + var T = arguments.length > 1 ? arguments[1] : undefined; + + /* Steps 8-9. */ + /* Steps a (implicit), and e. */ + /* Note: this will hang in some corner-case situations, because of IEEE-754 numbers' + * imprecision for large values. Example: + * var obj = { 18014398509481984: true, length: 18014398509481988 }; + * Array.prototype.find.call(obj, () => true); + */ + for (var k = 0; k < len; k++) { + /* Steps b and c (implicit) */ + if (k in O) { + /* Step d. */ + if (callFunction(predicate, T, O[k], k, O)) + return k; + } + } + + /* Step 10. */ + return -1; +} diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 69445d9467e1..73d9bc2b60d4 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2727,6 +2727,10 @@ static const JSFunctionSpec array_methods[] = { {"some", {NULL, NULL}, 1,0, "ArraySome"}, {"every", {NULL, NULL}, 1,0, "ArrayEvery"}, + /* ES6 additions */ + {"find", {NULL, NULL}, 1,0, "ArrayFind"}, + {"findIndex", {NULL, NULL}, 1,0, "ArrayFindIndex"}, + JS_FN("iterator", JS_ArrayIterator, 0,0), JS_FS_END }; diff --git a/js/src/tests/ecma_6/Array/browser.js b/js/src/tests/ecma_6/Array/browser.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/js/src/tests/ecma_6/Array/find_findindex.js b/js/src/tests/ecma_6/Array/find_findindex.js new file mode 100644 index 000000000000..ead157334aa1 --- /dev/null +++ b/js/src/tests/ecma_6/Array/find_findindex.js @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 885553; +var summary = 'Array.prototype.find and Array.prototype.findIndex'; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +function isString(v, index, array) +{ + reportCompare(v, array[index], 'isString: check callback argument consistency'); + return typeof v == 'string'; +} + +function dumpError(e) +{ + var s = e.name + ': ' + e.message + + ' File: ' + e.fileName + + ', Line: ' + e.lineNumber + + ', Stack: ' + e.stack; + return s; +} + +var expect; +var actual; +var obj; + +var strings = ['hello', 'Array', 'WORLD']; +var mixed = [0, '1', 2]; +var sparsestrings = new Array(); +sparsestrings[2] = 'sparse'; +var arraylike = {0:0, 1:'string', 2:2, length:3}; +// array for which JSObject::isIndexed() holds. +var indexedArray = []; +Object.defineProperty(indexedArray, 42, { get: function() { return 42; } }) + +// find and findIndex have 1 required argument + +expect = 1; +actual = Array.prototype.find.length; +reportCompare(expect, actual, 'Array.prototype.find.length == 1'); +actual = Array.prototype.findIndex.length; +reportCompare(expect, actual, 'Array.prototype.findIndex.length == 1'); + +// throw TypeError if no predicate specified +expect = 'TypeError'; +try +{ + strings.find(); + actual = 'no error'; +} +catch(e) +{ + actual = e.name; +} +reportCompare(expect, actual, 'Array.find(undefined) throws TypeError'); +try +{ + strings.findIndex(); + actual = 'no error'; +} +catch(e) +{ + actual = e.name; +} +reportCompare(expect, actual, 'Array.findIndex(undefined) throws TypeError'); + +// Length gets treated as integer, not uint32 +obj = { length: -4294967295, 0: 42 }; +expected = undefined; +actual = Array.prototype.find.call(obj, () => true); +reportCompare(expected, actual, 'find correctly treats "length" as an integer'); +expected = -1 +actual = Array.prototype.findIndex.call(obj, () => true); +reportCompare(expected, actual, 'findIndex correctly treats "length" as an integer'); + +// test find and findIndex results +try +{ + expect = 'hello'; + actual = strings.find(isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'strings: find finds first string element'); + +try +{ + expect = 0; + actual = strings.findIndex(isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'strings: findIndex finds first string element'); + +try +{ + expect = '1'; + actual = mixed.find(isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'mixed: find finds first string element'); + +try +{ + expect = 1; + actual = mixed.findIndex(isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'mixed: findIndex finds first string element'); + +try +{ + expect = 'sparse'; + actual = sparsestrings.find(isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'sparsestrings: find finds first string element'); + +try +{ + expect = 2; + actual = sparsestrings.findIndex(isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'sparsestrings: findIndex finds first string element'); + +try +{ + expect = 'string'; + actual = [].find.call(arraylike, isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'arraylike: find finds first string element'); + +try +{ + expect = 1; + actual = [].findIndex.call(arraylike, isString); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'arraylike: findIndex finds first string element'); + +try +{ + expect = 1; + actual = 0; + Array.prototype.find.call({get 0(){ actual++ }, length: 1}, ()=>true); +} +catch(e) +{ + actual = dumpError(e); +} +reportCompare(expect, actual, 'arraylike with getter: getter only called once'); \ No newline at end of file diff --git a/js/src/tests/ecma_6/Array/shell.js b/js/src/tests/ecma_6/Array/shell.js new file mode 100644 index 000000000000..e69de29bb2d1