Skip to content

Commit

Permalink
Add Array.prototype.flatMap polyfill and FlattenIntoArray helper
Browse files Browse the repository at this point in the history
  • Loading branch information
Jake Champion authored and JakeChampion committed Feb 4, 2019
1 parent 516d96e commit 1e382e2
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 0 deletions.
30 changes: 30 additions & 0 deletions polyfills/Array/prototype/flatMap/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"browsers": {
"android": "*",
"bb": "*",
"chrome": "*",
"edge": "*",
"edge_mob": "*",
"firefox": "*",
"firefox_mob": "*",
"ie": "*",
"ie_mob": "*",
"ios_chr": "*",
"ios_saf": "*",
"op_mini": "*",
"op_mob": "*",
"opera": "*",
"safari": "*",
"samsung_mob": "*"
},
"dependencies": [
"_ESAbstract.CreateMethodProperty",
"_ESAbstract.ToObject",
"_ESAbstract.ToLength",
"_ESAbstract.Get",
"_ESAbstract.IsCallable",
"_ESAbstract.ArraySpeciesCreate",
"_ESAbstract.FlattenIntoArray"
],
"spec": "https://tc39.github.io/ecma262/#sec-array.prototype.flatmap"
}
1 change: 1 addition & 0 deletions polyfills/Array/prototype/flatMap/detect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
'flatMap' in Array.prototype
25 changes: 25 additions & 0 deletions polyfills/Array/prototype/flatMap/polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* global CreateMethodProperty, ToObject, ToLength, Get, IsCallable, ArraySpeciesCreate, FlattenIntoArray */
// 22.1.3.11 Array.prototype.flatMap ( mapperFunction [ , thisArg ] )
CreateMethodProperty(Array.prototype, 'flatMap', function flatMap(mapperFunction /* , thisArg */ ) {
// 1. Let O be ? ToObject(this value).
var O = ToObject(this);
// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
var sourceLen = ToLength(Get(O, "length"));
// 3. If IsCallable(mapperFunction) is false, throw a TypeError exception.
if (IsCallable(mapperFunction) === false) {
throw new TypeError('mapperFunction is not callable.');
}
// 4. If thisArg is present, let T be thisArg; else let T be undefined.
var T;
if (1 in arguments) {
T = arguments[1];
} else {
T = undefined;
}
// 5. Let A be ? ArraySpeciesCreate(O, 0).
var A = ArraySpeciesCreate(O, 0);
// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T).
FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T);
// 7. Return A.
return A;
});
108 changes: 108 additions & 0 deletions polyfills/Array/prototype/flatMap/tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-env mocha */
/* globals proclaim */

it('is a function', function () {
proclaim.isFunction(Array.prototype.flatMap);
});

it('has correct arity', function () {
proclaim.arity(Array.prototype.flatMap, 1);
});

it('has correct name', function () {
proclaim.hasName(Array.prototype.flatMap, 'flatMap');
});

it('is not enumerable', function () {
proclaim.isNotEnumerable(Array.prototype, 'flatMap');
});

it('throws TypeError if thisArg is null', function () {
proclaim.throws(function () {
[].flatMap.call(null, function () {});
}, TypeError);
});

it('throws TypeError if thisArg is undefined', function () {
proclaim.throws(function () {
[].flatMap.call(undefined, function () {});
}, TypeError);
});

it('throws TypeError if argument is not callable', function () {
proclaim.throws(function () {
[].flatMap({});
}, TypeError);

proclaim.throws(function () {
[].flatMap(0);
}, TypeError);

proclaim.throws(function () {
[].flatMap();
}, TypeError);

proclaim.throws(function () {
[].flatMap(undefined);
}, TypeError);

proclaim.throws(function () {
[].flatMap(null);
}, TypeError);

proclaim.throws(function () {
[].flatMap(false);
}, TypeError);

proclaim.throws(function () {
[].flatMap('');
}, TypeError);
});

it('throws a TypeError if constructor property is neither undefined nor an Object', function () {
proclaim.throws(function () {
var a = [];
a.constructor = null;
a.flatMap(function() {});
}, TypeError);

proclaim.throws(function () {
var a = [];
a.constructor = 1;
a.flatMap(function() {});
}, TypeError);

proclaim.throws(function () {
var a = [];
a.constructor = 'string';
a.flatMap(function() {});
}, TypeError);

proclaim.throws(function () {
var a = [];
a.constructor = true;
a.flatMap(function() {});
}, TypeError);
});

it('calls mapper function for each item in the array and flattens one level deep if mapper function returns an array', function() {
var actual = [1, [2], [[3]], [[[4]]]].flatMap(function (item, index) {
return [item, index];
});

var expected = [1, 0, [2], 1, [[3]], 2, [[[4]]], 3];
proclaim.deepStrictEqual(actual, [1, 0, [2], 1, [[3]], 2, [[[4]]], 3]);
proclaim.deepStrictEqual(actual.length, expected.length);
});

it('can change context of mapper function with second argument', function() {
var actual = [1, 2, 3, 4].flatMap(function (item) {
return [this.x + item];
}, { x: 'hello '});

proclaim.deepStrictEqual(actual, ["hello 1", "hello 2", "hello 3", "hello 4"]);
});

it('fills in sparse/holey arrays with empty arrays', function () {
proclaim.deepStrictEqual([, [1]].flatMap(function (x) { return x; }), [[], [1]].flatMap( function (x) { return x; }));
});
30 changes: 30 additions & 0 deletions polyfills/_ESAbstract/FlattenIntoArray/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"browsers": {
"android": "*",
"bb": "*",
"chrome": "*",
"edge": "*",
"edge_mob": "*",
"firefox": "*",
"firefox_mob": "*",
"ie": "*",
"ie_mob": "*",
"ios_chr": "*",
"ios_saf": "*",
"op_mini": "*",
"op_mob": "*",
"opera": "*",
"safari": "*",
"samsung_mob": "*"
},
"dependencies": [
"_ESAbstract.ToString",
"_ESAbstract.HasProperty",
"_ESAbstract.Get",
"_ESAbstract.Call",
"_ESAbstract.IsArray",
"_ESAbstract.ToLength",
"_ESAbstract.CreateDataPropertyOrThrow"
],
"spec": "https://tc39.github.io/ecma262/#sec-flattenintoarray"
}
56 changes: 56 additions & 0 deletions polyfills/_ESAbstract/FlattenIntoArray/polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* globals ToString, HasProperty, Get, Call, IsArray, ToLength, CreateDataPropertyOrThrow */
// 22.1.3.10.1 FlattenIntoArray(target, source, sourceLen, start, depth [ , mapperFunction, thisArg ])
function FlattenIntoArray(target, source, sourceLen, start, depth /* , mapperFunction, thisArg */ ) { // eslint-disable-line no-unused-vars
var mapperFunction = arguments[5];
var thisArg = arguments[6];
// 1. Let targetIndex be start.
var targetIndex = start;
// 2. Let sourceIndex be 0.
var sourceIndex = 0;
// 3. Repeat, while sourceIndex < sourceLen
while (sourceIndex < sourceLen) {
// a. Let P be ! ToString(sourceIndex).
var P = ToString(sourceIndex);
// b. Let exists be ? HasProperty(source, P).
var exists = HasProperty(source, P);
// c. If exists is true, then
if (exists === true) {
// i. Let element be ? Get(source, P).
var element = Get(source, P);
// ii. If mapperFunction is present, then
if (5 in arguments) {
// 1. Assert: thisArg is present.
// 2. Set element to ? Call(mapperFunction, thisArg , « element, sourceIndex, source »).
element = Call(mapperFunction, thisArg, [element, sourceIndex, source]);
}
// iii. Let shouldFlatten be false.
var shouldFlatten = false;
// iv. If depth > 0, then
if (depth > 0) {
// 1. Set shouldFlatten to ? IsArray(element).
shouldFlatten = IsArray(element);
}
// v. If shouldFlatten is true, then
if (shouldFlatten === true) {
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
var elementLen = ToLength(Get(element, "length"));
// 2. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1).
targetIndex = FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1);
// vi. Else,
} else {
// 1. If targetIndex ≥ 253-1, throw a TypeError exception.
if (targetIndex >= (Math.pow(2, 53) - 1)) {
throw new TypeError("targetIndex is greater than or equal to 2^53-1");
}
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
CreateDataPropertyOrThrow(target, ToString(targetIndex), element);
// 3. Increase targetIndex by 1.
targetIndex += 1;
}
}
// d. Increase sourceIndex by 1.
sourceIndex += 1;
}
// 4. Return targetIndex.
return targetIndex;
}

0 comments on commit 1e382e2

Please sign in to comment.