From ae0cd69e7e24525ba9d8b4bff900548a44f859e7 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 3 Jun 2021 11:29:14 +0700 Subject: [PATCH 1/8] add polyfill of stable sort --- CHANGELOG.md | 1 + README.md | 4 +- packages/core-js-compat/src/data.js | 8 +- packages/core-js/internals/array-sort.js | 114 ++++++++++++++++++ packages/core-js/modules/es.array.sort.js | 29 +---- .../core-js/modules/es.typed-array.sort.js | 26 +++- tests/compat/tests.js | 47 +++++++- tests/pure/es.array.sort.js | 42 +++++++ tests/tests/es.array.sort.js | 42 +++++++ tests/tests/es.typed-array.sort.js | 63 ++++++++++ 10 files changed, 339 insertions(+), 37 deletions(-) create mode 100644 packages/core-js/internals/array-sort.js create mode 100644 tests/tests/es.typed-array.sort.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c8239118f990..a0f6aced21f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Changelog ##### Unreleased +- Added polyfill of stable sort in `{ Array, %TypedArray% }.prototype.sort`, [#769](https://github.com/zloirock/core-js/issues/769) - `.at` marked as supported from V8 9.2 ##### 3.13.1 - 2021.05.29 diff --git a/README.md b/README.md index e8c415394bc7..509ec6da5b8e 100644 --- a/README.md +++ b/README.md @@ -541,7 +541,7 @@ class Array { slice(start?: number, end?: number): Array; // with adding support of @@species splice(start?: number, deleteCount?: number, ...items: Array): Array; // with adding support of @@species some(callbackfn: (value: any, index: number, target: any) => boolean, thisArg?: any): boolean; - sort(comparefn?: (a: any, b: any) => number): this; + sort(comparefn?: (a: any, b: any) => number): this; // with modern behavior like stable sort values(): Iterator; @@iterator(): Iterator; @@unscopables: { [newMethodNames: string]: true }; @@ -1510,7 +1510,7 @@ class %TypedArray% { set(array: ArrayLike, offset?: number): void; slice(start?: number, end?: number): %TypedArray%; some(callbackfn: (value: number, index: number, target: %TypedArray%) => boolean, thisArg?: any): boolean; - sort(comparefn?: (a: number, b: number) => number): this; + sort(comparefn?: (a: number, b: number) => number): this; // with modern behavior like stable sort subarray(begin?: number, end?: number): %TypedArray%; toString(): string; toLocaleString(): string; diff --git a/packages/core-js-compat/src/data.js b/packages/core-js-compat/src/data.js index 3c683d3684f8..8568cd7584b4 100644 --- a/packages/core-js-compat/src/data.js +++ b/packages/core-js-compat/src/data.js @@ -236,9 +236,8 @@ const data = { safari: '8.0', }, 'es.array.sort': { - chrome: '63', + chrome: '70', firefox: '4', - ie: '9', safari: '12.0', }, 'es.array.species': { @@ -1229,9 +1228,8 @@ const data = { safari: '10.0', }, 'es.typed-array.sort': { - chrome: '45', - edge: '13', - firefox: '46', + chrome: '74', + firefox: '67', safari: '10.0', }, 'es.typed-array.subarray': { diff --git a/packages/core-js/internals/array-sort.js b/packages/core-js/internals/array-sort.js new file mode 100644 index 000000000000..7e4af389b92e --- /dev/null +++ b/packages/core-js/internals/array-sort.js @@ -0,0 +1,114 @@ +'use strict'; +var aFunction = require('../internals/a-function'); +var toObject = require('../internals/to-object'); +var toLength = require('../internals/to-length'); +var fails = require('../internals/fails'); +var arrayMethodIsStrict = require('../internals/array-method-is-strict'); + +var test = []; +var nativeSort = test.sort; +var floor = Math.floor; + +// IE8- +var FAILS_ON_UNDEFINED = fails(function () { + test.sort(undefined); +}); +// V8 bug +var FAILS_ON_NULL = fails(function () { + test.sort(null); +}); +// Old WebKit +var STRICT_METHOD = arrayMethodIsStrict('sort'); + +var STABLE_SORT = !fails(function () { + var result = ''; + var code, chr, value, index; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + test.push({ k: chr + index, v: value }); + } + } + + test.sort(function (a, b) { return b.v - a.v; }); + + for (index = 0; index < test.length; index++) { + chr = test[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + return result !== 'DGBEFHACIJK'; +}); + +var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; + +var mergeSort = function (array, comparefn) { + var length = array.length; + var middle = floor(length / 2); + if (length < 2) return array; + return merge( + mergeSort(array.slice(0, middle), comparefn), + mergeSort(array.slice(middle), comparefn), + comparefn + ); +}; + +var merge = function (left, right, comparefn) { + var llength = left.length; + var rlength = right.length; + var lindex = 0; + var rindex = 0; + var result = []; + + while (lindex < llength || rindex < rlength) { + if (lindex < llength && rindex < rlength) { + result.push(sortCompare(left[lindex], right[rindex], comparefn) <= 0 ? left[lindex++] : right[rindex++]); + } else { + result.push(lindex < llength ? left[lindex++] : right[rindex++]); + } + } return result; +}; + +var sortCompare = function (x, y, comparefn) { + if (x === undefined && y === undefined) return 0; + if (x === undefined) return 1; + if (y === undefined) return -1; + if (comparefn !== undefined) { + return +comparefn(x, y) || 0; + } return String(x) > String(y) ? 1 : -1; +}; + +// `Array.prototype.sort` method +// https://tc39.es/ecma262/#sec-array.prototype.sort +module.exports = FORCED ? function sort(comparefn) { + if (comparefn !== undefined) aFunction(comparefn); + + var array = toObject(this); + + if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn); + + var items = []; + var arrayLength = toLength(array.length); + var itemsLength, index; + + for (index = 0; index < arrayLength; index++) { + if (index in array) items.push(array[index]); + } + + items = mergeSort(items, comparefn); + itemsLength = items.length; + index = 0; + + while (index < itemsLength) array[index] = items[index++]; + while (index < arrayLength) delete array[index++]; + + return array; +} : nativeSort; diff --git a/packages/core-js/modules/es.array.sort.js b/packages/core-js/modules/es.array.sort.js index fd36157804a2..7e27a9e01582 100644 --- a/packages/core-js/modules/es.array.sort.js +++ b/packages/core-js/modules/es.array.sort.js @@ -1,32 +1,9 @@ 'use strict'; var $ = require('../internals/export'); -var aFunction = require('../internals/a-function'); -var toObject = require('../internals/to-object'); -var fails = require('../internals/fails'); -var arrayMethodIsStrict = require('../internals/array-method-is-strict'); - -var test = []; -var nativeSort = test.sort; - -// IE8- -var FAILS_ON_UNDEFINED = fails(function () { - test.sort(undefined); -}); -// V8 bug -var FAILS_ON_NULL = fails(function () { - test.sort(null); -}); -// Old WebKit -var STRICT_METHOD = arrayMethodIsStrict('sort'); - -var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD; +var sort = require('../internals/array-sort'); // `Array.prototype.sort` method // https://tc39.es/ecma262/#sec-array.prototype.sort -$({ target: 'Array', proto: true, forced: FORCED }, { - sort: function sort(comparefn) { - return comparefn === undefined - ? nativeSort.call(toObject(this)) - : nativeSort.call(toObject(this), aFunction(comparefn)); - } +$({ target: 'Array', proto: true, forced: [].sort !== sort }, { + sort: sort }); diff --git a/packages/core-js/modules/es.typed-array.sort.js b/packages/core-js/modules/es.typed-array.sort.js index 46cc25e4b0ac..a22e95b09b59 100644 --- a/packages/core-js/modules/es.typed-array.sort.js +++ b/packages/core-js/modules/es.typed-array.sort.js @@ -1,12 +1,34 @@ 'use strict'; var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); +var fails = require('../internals/fails'); +var $sort = require('../internals/array-sort'); var aTypedArray = ArrayBufferViewCore.aTypedArray; var exportTypedArrayMethod = ArrayBufferViewCore.exportTypedArrayMethod; -var $sort = [].sort; + +var STABLE_SORT = !fails(function () { + // eslint-disable-next-line es/no-typed-arrays -- required for testing + var array = new Uint16Array(516); + var expected = Array(516); + var index, mod; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort(function (a, b) { + return (a / 4 | 0) - (b / 4 | 0); + }); + + for (index = 0; index < 516; index++) { + if (array[index] !== expected[index]) return true; + } +}); // `%TypedArray%.prototype.sort` method // https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort exportTypedArrayMethod('sort', function sort(comparefn) { return $sort.call(aTypedArray(this), comparefn); -}); +}, !STABLE_SORT); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index ba30ae67c239..2c9403287e96 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -382,7 +382,33 @@ GLOBAL.tests = { [1, 2, 3].sort(null); } catch (error2) { [1, 2, 3].sort(undefined); - return true; + + // stable sort + var array = []; + var result = ''; + var code, chr, value, index; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + for (index = 0; index < 47; index++) { + array.push({ k: chr + index, v: value }); + } + } + + array.sort(function (a, b) { return b.v - a.v; }); + + for (index = 0; index < array.length; index++) { + chr = array[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + return result === 'DGBEFHACIJK'; } } }, @@ -1039,7 +1065,24 @@ GLOBAL.tests = { return Int8Array.prototype.some; }], 'es.typed-array.sort': [ARRAY_BUFFER_VIEWS_SUPPORT, function () { - return Int8Array.prototype.sort; + // stable sort + var array = new Uint16Array(516); + var expected = Array(516); + var index, mod; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort(function (a, b) { + return (a / 4 | 0) - (b / 4 | 0); + }); + + for (index = 0; index < 516; index++) { + if (array[index] !== expected[index]) return; + } return true; }], 'es.typed-array.subarray': [ARRAY_BUFFER_VIEWS_SUPPORT, function () { return Int8Array.prototype.subarray; diff --git a/tests/pure/es.array.sort.js b/tests/pure/es.array.sort.js index 73074a0c14dc..5dce031e3f8c 100644 --- a/tests/pure/es.array.sort.js +++ b/tests/pure/es.array.sort.js @@ -7,6 +7,48 @@ QUnit.test('Array#sort', assert => { assert.notThrows(() => sort([1, 2, 3], undefined), 'works with undefined'); assert.throws(() => sort([1, 2, 3], null), 'throws on null'); assert.throws(() => sort([1, 2, 3], {}), 'throws on {}'); + + const expected = Array(516); + let array = Array(516); + let index, mod, code, chr, value; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + sort(array, (a, b) => (a / 4 | 0) - (b / 4 | 0)); + + assert.same(String(array), String(expected), 'stable #1'); + + let result = ''; + array = []; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + array.push({ k: chr + index, v: value }); + } + } + + sort(array, (a, b) => b.v - a.v); + + for (index = 0; index < array.length; index++) { + chr = array[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + assert.same(result, 'DGBEFHACIJK', 'stable #2'); + if (STRICT) { assert.throws(() => sort(null), TypeError, 'ToObject(this)'); assert.throws(() => sort(undefined), TypeError, 'ToObject(this)'); diff --git a/tests/tests/es.array.sort.js b/tests/tests/es.array.sort.js index c6bb7c9d9043..ee7a275fb361 100644 --- a/tests/tests/es.array.sort.js +++ b/tests/tests/es.array.sort.js @@ -7,6 +7,48 @@ QUnit.test('Array#sort', assert => { assert.name(sort, 'sort'); assert.looksNative(sort); assert.nonEnumerable(Array.prototype, 'sort'); + + const expected = Array(516); + let array = Array(516); + let index, mod, code, chr, value; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort((a, b) => (a / 4 | 0) - (b / 4 | 0)); + + assert.same(String(array), String(expected), 'stable #1'); + + let result = ''; + array = []; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + array.push({ k: chr + index, v: value }); + } + } + + array.sort((a, b) => b.v - a.v); + + for (index = 0; index < array.length; index++) { + chr = array[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + assert.same(result, 'DGBEFHACIJK', 'stable #2'); + assert.notThrows(() => [1, 2, 3].sort(undefined).length === 3, 'works with undefined'); assert.throws(() => [1, 2, 3].sort(null), 'throws on null'); assert.throws(() => [1, 2, 3].sort({}), 'throws on {}'); diff --git a/tests/tests/es.typed-array.sort.js b/tests/tests/es.typed-array.sort.js new file mode 100644 index 000000000000..3182cf3c2966 --- /dev/null +++ b/tests/tests/es.typed-array.sort.js @@ -0,0 +1,63 @@ +import { DESCRIPTORS, GLOBAL, STRICT, TYPED_ARRAYS } from '../helpers/constants'; + +if (DESCRIPTORS) QUnit.test('%TypedArrayPrototype%.sort', assert => { + // we can't implement %TypedArrayPrototype% in all engines, so run all tests for each typed array constructor + for (const name in TYPED_ARRAYS) { + const TypedArray = GLOBAL[name]; + const { sort } = TypedArray.prototype; + assert.isFunction(sort, `${ name }::sort is function`); + assert.arity(sort, 1, `${ name }::sort arity is 1`); + assert.name(sort, 'sort', `${ name }::sort name is 'sort'`); + assert.looksNative(sort, `${ name }::sort looks native`); + + if (name.indexOf('8') === -1) { + const expected = Array(516); + let array = new TypedArray(516); + let index, mod, j, k, postfix; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort((a, b) => (a / 4 | 0) - (b / 4 | 0)); + + assert.same(String(array), String(expected), 'stable #1'); + + let result = ''; + array = new TypedArray(520); + index = 0; + + for (j = 0; j < 10; j++) { + switch (j) { + case 1: case 4: case 5: case 7: postfix = 3; break; + case 3: case 6: postfix = 4; break; + default: postfix = 2; + } + + for (k = 0; k < 52; k++) { + array[index] = 10 * index++ + postfix; + } + } + + array.sort((a, b) => b % 10 - a % 10); + + for (index = 0; index < array.length; index++) { + j = String((array[index] / 520) | 0); + if (result.charAt(result.length - 1) !== j) result += j; + } + + assert.same(result, '3614570289', 'stable #2'); + } + + assert.throws(() => sort.call([0], () => { /* empty */ }), "isn't generic"); + assert.notThrows(() => new TypedArray([1, 2, 3]).sort(undefined).length === 3, 'works with undefined'); + assert.throws(() => new TypedArray([1, 2, 3]).sort(null), 'throws on null'); + assert.throws(() => new TypedArray([1, 2, 3]).sort({}), 'throws on {}'); + if (STRICT) { + assert.throws(() => sort.call(null), TypeError, 'ToObject(this)'); + assert.throws(() => sort.call(undefined), TypeError, 'ToObject(this)'); + } + } +}); From 8b56b388f1225aeb449819367477d596741c5783 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 10:33:11 +0700 Subject: [PATCH 2/8] improve polyfill of stable sort --- packages/core-js/internals/array-sort.js | 32 ++++++++++++++++--- .../core-js/internals/engine-ff-version.js | 5 +++ .../core-js/internals/engine-is-ie-or-edge.js | 3 ++ .../internals/engine-webkit-version.js | 5 +++ .../object-prototype-accessors-forced.js | 5 ++- .../core-js/modules/es.typed-array.sort.js | 10 ++++++ 6 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 packages/core-js/internals/engine-ff-version.js create mode 100644 packages/core-js/internals/engine-is-ie-or-edge.js create mode 100644 packages/core-js/internals/engine-webkit-version.js diff --git a/packages/core-js/internals/array-sort.js b/packages/core-js/internals/array-sort.js index 7e4af389b92e..dd9f9057ba40 100644 --- a/packages/core-js/internals/array-sort.js +++ b/packages/core-js/internals/array-sort.js @@ -4,6 +4,10 @@ var toObject = require('../internals/to-object'); var toLength = require('../internals/to-length'); var fails = require('../internals/fails'); var arrayMethodIsStrict = require('../internals/array-method-is-strict'); +var FF = require('../internals/engine-ff-version'); +var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); +var V8 = require('../internals/engine-v8-version'); +var WEBKIT = require('../internals/engine-webkit-version'); var test = []; var nativeSort = test.sort; @@ -21,6 +25,12 @@ var FAILS_ON_NULL = fails(function () { var STRICT_METHOD = arrayMethodIsStrict('sort'); var STABLE_SORT = !fails(function () { + // feature detection can be too slow, so check engines versions + if (V8) return V8 < 70; + if (FF && FF > 3) return; + if (IE_OR_EDGE) return true; + if (WEBKIT) return WEBKIT < 603; + var result = ''; var code, chr, value, index; @@ -53,14 +63,28 @@ var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_S var mergeSort = function (array, comparefn) { var length = array.length; var middle = floor(length / 2); - if (length < 2) return array; - return merge( + return length < 8 ? insertionSort(array, comparefn) : merge( mergeSort(array.slice(0, middle), comparefn), mergeSort(array.slice(middle), comparefn), comparefn ); }; +var insertionSort = function (array, comparefn) { + var length = array.length; + var i = 1; + var element, j; + + while (i < length) { + j = i; + element = array[i]; + while (j && sortCompare(array[j - 1], element, comparefn) > 0) { + array[j] = array[--j]; + } + if (j !== i++) array[j] = element; + } return array; +}; + var merge = function (left, right, comparefn) { var llength = left.length; var rlength = right.length; @@ -78,9 +102,8 @@ var merge = function (left, right, comparefn) { }; var sortCompare = function (x, y, comparefn) { - if (x === undefined && y === undefined) return 0; - if (x === undefined) return 1; if (y === undefined) return -1; + if (x === undefined) return 1; if (comparefn !== undefined) { return +comparefn(x, y) || 0; } return String(x) > String(y) ? 1 : -1; @@ -103,6 +126,7 @@ module.exports = FORCED ? function sort(comparefn) { if (index in array) items.push(array[index]); } + // TODO: use something more complex like timsort? items = mergeSort(items, comparefn); itemsLength = items.length; index = 0; diff --git a/packages/core-js/internals/engine-ff-version.js b/packages/core-js/internals/engine-ff-version.js new file mode 100644 index 000000000000..83ba0ffb9e45 --- /dev/null +++ b/packages/core-js/internals/engine-ff-version.js @@ -0,0 +1,5 @@ +var userAgent = require('../internals/engine-user-agent'); + +var firefox = userAgent.match(/firefox\/(\d+)/i); + +module.exports = !!firefox && +firefox[1]; diff --git a/packages/core-js/internals/engine-is-ie-or-edge.js b/packages/core-js/internals/engine-is-ie-or-edge.js new file mode 100644 index 000000000000..813c745eda81 --- /dev/null +++ b/packages/core-js/internals/engine-is-ie-or-edge.js @@ -0,0 +1,3 @@ +var UA = require('../internals/engine-user-agent'); + +module.exports = /MSIE|Trident/.test(UA); diff --git a/packages/core-js/internals/engine-webkit-version.js b/packages/core-js/internals/engine-webkit-version.js new file mode 100644 index 000000000000..55f7d47807bd --- /dev/null +++ b/packages/core-js/internals/engine-webkit-version.js @@ -0,0 +1,5 @@ +var userAgent = require('../internals/engine-user-agent'); + +var webkit = userAgent.match(/AppleWebKit\/(\d+)\./); + +module.exports = !!webkit && +webkit[1]; diff --git a/packages/core-js/internals/object-prototype-accessors-forced.js b/packages/core-js/internals/object-prototype-accessors-forced.js index fb45bd2f212c..2d65faa9aa05 100644 --- a/packages/core-js/internals/object-prototype-accessors-forced.js +++ b/packages/core-js/internals/object-prototype-accessors-forced.js @@ -2,14 +2,13 @@ var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var fails = require('../internals/fails'); -var userAgent = require('../internals/engine-user-agent'); +var WEBKIT = require('../internals/engine-webkit-version'); // Forced replacement object prototype accessors methods module.exports = IS_PURE || !fails(function () { // This feature detection crashes old WebKit // https://github.com/zloirock/core-js/issues/232 - var webkit = userAgent.match(/AppleWebKit\/(\d+)\./); - if (webkit && +webkit[1] < 535) return; + if (WEBKIT && WEBKIT < 535) return; var key = Math.random(); // In FF throws only define methods // eslint-disable-next-line no-undef, no-useless-call -- required for testing diff --git a/packages/core-js/modules/es.typed-array.sort.js b/packages/core-js/modules/es.typed-array.sort.js index a22e95b09b59..f234c12adf0f 100644 --- a/packages/core-js/modules/es.typed-array.sort.js +++ b/packages/core-js/modules/es.typed-array.sort.js @@ -2,11 +2,21 @@ var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); var fails = require('../internals/fails'); var $sort = require('../internals/array-sort'); +var FF = require('../internals/engine-ff-version'); +var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); +var V8 = require('../internals/engine-v8-version'); +var WEBKIT = require('../internals/engine-webkit-version'); var aTypedArray = ArrayBufferViewCore.aTypedArray; var exportTypedArrayMethod = ArrayBufferViewCore.exportTypedArrayMethod; var STABLE_SORT = !fails(function () { + // feature detection can be too slow, so check engines versions + if (V8) return V8 < 73; + if (FF) return FF < 67; + if (IE_OR_EDGE) return true; + if (WEBKIT) return WEBKIT < 602; + // eslint-disable-next-line es/no-typed-arrays -- required for testing var array = new Uint16Array(516); var expected = Array(516); From 39919bedda65ffab1ac42ed5e1289ab91fec269c Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 17:52:32 +0700 Subject: [PATCH 3/8] add some more tests --- tests/pure/es.array.sort.js | 64 ++++++++++++++++++++++++++++++++++-- tests/tests/es.array.sort.js | 64 ++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/tests/pure/es.array.sort.js b/tests/pure/es.array.sort.js index 5dce031e3f8c..a01e12878400 100644 --- a/tests/pure/es.array.sort.js +++ b/tests/pure/es.array.sort.js @@ -8,9 +8,69 @@ QUnit.test('Array#sort', assert => { assert.throws(() => sort([1, 2, 3], null), 'throws on null'); assert.throws(() => sort([1, 2, 3], {}), 'throws on {}'); - const expected = Array(516); - let array = Array(516); + assert.deepEqual(sort([1, 3, 2]), [1, 2, 3], '#1'); + assert.deepEqual(sort([1, 3, 2, 11]), [1, 11, 2, 3], '#2'); + + let array = Array(5); + array[0] = 1; + array[2] = 3; + array[4] = 2; + let expected = Array(5); + expected[0] = 1; + expected[1] = 2; + expected[2] = 3; + assert.deepEqual(sort(array), expected, 'holes'); + + array = 'zyxwvutsrqponMLKJIHGFEDCBA'.split(''); + expected = 'ABCDEFGHIJKLMnopqrstuvwxyz'.split(''); + assert.deepEqual(sort(array), expected, 'alpha #1'); + + array = 'ёяюэьыъщшчцхфутсрПОНМЛКЙИЗЖЕДГВБА'.split(''); + expected = 'АБВГДЕЖЗИЙКЛМНОПрстуфхцчшщъыьэюяё'.split(''); + assert.deepEqual(sort(array), expected, 'alpha #2'); + + array = [undefined, 1]; + assert.notThrows(() => sort(array, () => { throw 1; }), 'undefined #1'); + assert.deepEqual(array, [1, undefined], 'undefined #2'); + + const object = { + valueOf: () => 1, + toString: () => -1, + }; + + array = { + 0: undefined, + 1: 2, + 2: 1, + 3: 'X', + 4: -1, + 5: 'a', + 6: true, + 7: object, + 8: NaN, + 10: Infinity, + length: 11, + }; + + expected = { + 0: -1, + 1: object, + 2: 1, + 3: 2, + 4: Infinity, + 5: NaN, + 6: 'X', + 7: 'a', + 8: true, + 9: undefined, + length: 11, + }; + + assert.deepEqual(sort(array), expected, 'custom generic'); + let index, mod, code, chr, value; + expected = Array(516); + array = Array(516); for (index = 0; index < 516; index++) { mod = index % 4; diff --git a/tests/tests/es.array.sort.js b/tests/tests/es.array.sort.js index ee7a275fb361..94aa6f30d72b 100644 --- a/tests/tests/es.array.sort.js +++ b/tests/tests/es.array.sort.js @@ -8,9 +8,69 @@ QUnit.test('Array#sort', assert => { assert.looksNative(sort); assert.nonEnumerable(Array.prototype, 'sort'); - const expected = Array(516); - let array = Array(516); + assert.deepEqual([1, 3, 2].sort(), [1, 2, 3], '#1'); + assert.deepEqual([1, 3, 2, 11].sort(), [1, 11, 2, 3], '#2'); + + let array = Array(5); + array[0] = 1; + array[2] = 3; + array[4] = 2; + let expected = Array(5); + expected[0] = 1; + expected[1] = 2; + expected[2] = 3; + assert.deepEqual(array.sort(), expected, 'holes'); + + array = 'zyxwvutsrqponMLKJIHGFEDCBA'.split(''); + expected = 'ABCDEFGHIJKLMnopqrstuvwxyz'.split(''); + assert.deepEqual(array.sort(), expected, 'alpha #1'); + + array = 'ёяюэьыъщшчцхфутсрПОНМЛКЙИЗЖЕДГВБА'.split(''); + expected = 'АБВГДЕЖЗИЙКЛМНОПрстуфхцчшщъыьэюяё'.split(''); + assert.deepEqual(array.sort(), expected, 'alpha #2'); + + array = [undefined, 1]; + assert.notThrows(() => array.sort(() => { throw 1; }), 'undefined #1'); + assert.deepEqual(array, [1, undefined], 'undefined #2'); + + const object = { + valueOf: () => 1, + toString: () => -1, + }; + + array = { + 0: undefined, + 1: 2, + 2: 1, + 3: 'X', + 4: -1, + 5: 'a', + 6: true, + 7: object, + 8: NaN, + 10: Infinity, + length: 11, + }; + + expected = { + 0: -1, + 1: object, + 2: 1, + 3: 2, + 4: Infinity, + 5: NaN, + 6: 'X', + 7: 'a', + 8: true, + 9: undefined, + length: 11, + }; + + assert.deepEqual(sort.call(array), expected, 'custom generic'); + let index, mod, code, chr, value; + expected = Array(516); + array = Array(516); for (index = 0; index < 516; index++) { mod = index % 4; From e7e81d87bbe9dffeff3d1cffa804ba8fbf15c52e Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 19:15:44 +0700 Subject: [PATCH 4/8] Safari 10.0 - 14.0 `%TypedArray%.prototype.sort` accept incorrect arguments --- CHANGELOG.md | 1 + packages/core-js-compat/src/data.js | 3 ++- .../core-js/modules/es.typed-array.sort.js | 25 ++++++++++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f6aced21f0..94ba27df769f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Changelog ##### Unreleased - Added polyfill of stable sort in `{ Array, %TypedArray% }.prototype.sort`, [#769](https://github.com/zloirock/core-js/issues/769) +- Fixed `Safari` 14.0- `{ Array, %TypedArray% }.prototype.sort` validation of arguments bug - `.at` marked as supported from V8 9.2 ##### 3.13.1 - 2021.05.29 diff --git a/packages/core-js-compat/src/data.js b/packages/core-js-compat/src/data.js index 8568cd7584b4..871e028c1815 100644 --- a/packages/core-js-compat/src/data.js +++ b/packages/core-js-compat/src/data.js @@ -1230,7 +1230,8 @@ const data = { 'es.typed-array.sort': { chrome: '74', firefox: '67', - safari: '10.0', + // 10.0 - 14.0 accept incorrect arguments + safari: '14.1', }, 'es.typed-array.subarray': { chrome: '26', diff --git a/packages/core-js/modules/es.typed-array.sort.js b/packages/core-js/modules/es.typed-array.sort.js index f234c12adf0f..541ad7041985 100644 --- a/packages/core-js/modules/es.typed-array.sort.js +++ b/packages/core-js/modules/es.typed-array.sort.js @@ -1,7 +1,10 @@ 'use strict'; +/* eslint-disable es/no-typed-arrays -- required for testing */ var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); +var global = require('../internals/global'); var fails = require('../internals/fails'); -var $sort = require('../internals/array-sort'); +var aFunction = require('../internals/a-function'); +var arraySort = require('../internals/array-sort'); var FF = require('../internals/engine-ff-version'); var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); var V8 = require('../internals/engine-v8-version'); @@ -9,15 +12,23 @@ var WEBKIT = require('../internals/engine-webkit-version'); var aTypedArray = ArrayBufferViewCore.aTypedArray; var exportTypedArrayMethod = ArrayBufferViewCore.exportTypedArrayMethod; +var Uint16Array = global.Uint16Array; +var nativeSort = Uint16Array && Uint16Array.prototype.sort; -var STABLE_SORT = !fails(function () { +// WebKit +var ACCEPT_INCORRECT_ARGUMENTS = !!nativeSort && !fails(function () { + var array = new Uint16Array(2); + array.sort(null); + array.sort({}); +}); + +var STABLE_SORT = !!nativeSort && !fails(function () { // feature detection can be too slow, so check engines versions - if (V8) return V8 < 73; + if (V8) return V8 < 74; if (FF) return FF < 67; if (IE_OR_EDGE) return true; if (WEBKIT) return WEBKIT < 602; - // eslint-disable-next-line es/no-typed-arrays -- required for testing var array = new Uint16Array(516); var expected = Array(516); var index, mod; @@ -40,5 +51,7 @@ var STABLE_SORT = !fails(function () { // `%TypedArray%.prototype.sort` method // https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort exportTypedArrayMethod('sort', function sort(comparefn) { - return $sort.call(aTypedArray(this), comparefn); -}, !STABLE_SORT); + if (!STABLE_SORT) return arraySort.call(aTypedArray(this), comparefn); + if (comparefn !== undefined) aFunction(comparefn); + return nativeSort.call(this, comparefn); +}, !STABLE_SORT || ACCEPT_INCORRECT_ARGUMENTS); From 07ca565c9fa14ee851d31133a02a984371d0b81e Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 19:32:14 +0700 Subject: [PATCH 5/8] update compat data tests --- packages/core-js/modules/es.typed-array.sort.js | 1 - tests/compat/tests.js | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core-js/modules/es.typed-array.sort.js b/packages/core-js/modules/es.typed-array.sort.js index 541ad7041985..6f2b39784b82 100644 --- a/packages/core-js/modules/es.typed-array.sort.js +++ b/packages/core-js/modules/es.typed-array.sort.js @@ -1,5 +1,4 @@ 'use strict'; -/* eslint-disable es/no-typed-arrays -- required for testing */ var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); var global = require('../internals/global'); var fails = require('../internals/fails'); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 2c9403287e96..93a4a83f470a 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1065,6 +1065,11 @@ GLOBAL.tests = { return Int8Array.prototype.some; }], 'es.typed-array.sort': [ARRAY_BUFFER_VIEWS_SUPPORT, function () { + try { + new Uint16Array(1).sort(null); + new Uint16Array(1).sort({}); + return false; + } catch (error) { /* empty */ } // stable sort var array = new Uint16Array(516); var expected = Array(516); From 586d57830732220e349293286acafebd29c05182 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 21:05:21 +0700 Subject: [PATCH 6/8] make `%TypedArray%.prototype.sort` stricter --- packages/core-js/internals/array-sort.js | 99 +------------------ packages/core-js/modules/es.array.sort.js | 99 ++++++++++++++++++- .../core-js/modules/es.typed-array.sort.js | 36 ++++++- tests/pure/es.array.sort.js | 2 + tests/tests/es.array.sort.js | 2 + tests/tests/es.typed-array.sort.js | 5 + 6 files changed, 141 insertions(+), 102 deletions(-) diff --git a/packages/core-js/internals/array-sort.js b/packages/core-js/internals/array-sort.js index dd9f9057ba40..c815d7ce3158 100644 --- a/packages/core-js/internals/array-sort.js +++ b/packages/core-js/internals/array-sort.js @@ -1,65 +1,6 @@ 'use strict'; -var aFunction = require('../internals/a-function'); -var toObject = require('../internals/to-object'); -var toLength = require('../internals/to-length'); -var fails = require('../internals/fails'); -var arrayMethodIsStrict = require('../internals/array-method-is-strict'); -var FF = require('../internals/engine-ff-version'); -var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); -var V8 = require('../internals/engine-v8-version'); -var WEBKIT = require('../internals/engine-webkit-version'); - -var test = []; -var nativeSort = test.sort; var floor = Math.floor; -// IE8- -var FAILS_ON_UNDEFINED = fails(function () { - test.sort(undefined); -}); -// V8 bug -var FAILS_ON_NULL = fails(function () { - test.sort(null); -}); -// Old WebKit -var STRICT_METHOD = arrayMethodIsStrict('sort'); - -var STABLE_SORT = !fails(function () { - // feature detection can be too slow, so check engines versions - if (V8) return V8 < 70; - if (FF && FF > 3) return; - if (IE_OR_EDGE) return true; - if (WEBKIT) return WEBKIT < 603; - - var result = ''; - var code, chr, value, index; - - // generate an array with more 512 elements (Chakra and old V8 fails only in this case) - for (code = 65; code < 76; code++) { - chr = String.fromCharCode(code); - switch (code) { - case 66: case 69: case 70: case 72: value = 3; break; - case 68: case 71: value = 4; break; - default: value = 2; - } - - for (index = 0; index < 47; index++) { - test.push({ k: chr + index, v: value }); - } - } - - test.sort(function (a, b) { return b.v - a.v; }); - - for (index = 0; index < test.length; index++) { - chr = test[index].k.charAt(0); - if (result.charAt(result.length - 1) !== chr) result += chr; - } - - return result !== 'DGBEFHACIJK'; -}); - -var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; - var mergeSort = function (array, comparefn) { var length = array.length; var middle = floor(length / 2); @@ -78,7 +19,7 @@ var insertionSort = function (array, comparefn) { while (i < length) { j = i; element = array[i]; - while (j && sortCompare(array[j - 1], element, comparefn) > 0) { + while (j && comparefn(array[j - 1], element) > 0) { array[j] = array[--j]; } if (j !== i++) array[j] = element; @@ -94,45 +35,11 @@ var merge = function (left, right, comparefn) { while (lindex < llength || rindex < rlength) { if (lindex < llength && rindex < rlength) { - result.push(sortCompare(left[lindex], right[rindex], comparefn) <= 0 ? left[lindex++] : right[rindex++]); + result.push(comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++]); } else { result.push(lindex < llength ? left[lindex++] : right[rindex++]); } } return result; }; -var sortCompare = function (x, y, comparefn) { - if (y === undefined) return -1; - if (x === undefined) return 1; - if (comparefn !== undefined) { - return +comparefn(x, y) || 0; - } return String(x) > String(y) ? 1 : -1; -}; - -// `Array.prototype.sort` method -// https://tc39.es/ecma262/#sec-array.prototype.sort -module.exports = FORCED ? function sort(comparefn) { - if (comparefn !== undefined) aFunction(comparefn); - - var array = toObject(this); - - if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn); - - var items = []; - var arrayLength = toLength(array.length); - var itemsLength, index; - - for (index = 0; index < arrayLength; index++) { - if (index in array) items.push(array[index]); - } - - // TODO: use something more complex like timsort? - items = mergeSort(items, comparefn); - itemsLength = items.length; - index = 0; - - while (index < itemsLength) array[index] = items[index++]; - while (index < arrayLength) delete array[index++]; - - return array; -} : nativeSort; +module.exports = mergeSort; diff --git a/packages/core-js/modules/es.array.sort.js b/packages/core-js/modules/es.array.sort.js index 7e27a9e01582..551a5a1bd8b6 100644 --- a/packages/core-js/modules/es.array.sort.js +++ b/packages/core-js/modules/es.array.sort.js @@ -1,9 +1,102 @@ 'use strict'; var $ = require('../internals/export'); -var sort = require('../internals/array-sort'); +var aFunction = require('../internals/a-function'); +var toObject = require('../internals/to-object'); +var toLength = require('../internals/to-length'); +var fails = require('../internals/fails'); +var internalSort = require('../internals/array-sort'); +var arrayMethodIsStrict = require('../internals/array-method-is-strict'); +var FF = require('../internals/engine-ff-version'); +var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); +var V8 = require('../internals/engine-v8-version'); +var WEBKIT = require('../internals/engine-webkit-version'); + +var test = []; +var nativeSort = test.sort; + +// IE8- +var FAILS_ON_UNDEFINED = fails(function () { + test.sort(undefined); +}); +// V8 bug +var FAILS_ON_NULL = fails(function () { + test.sort(null); +}); +// Old WebKit +var STRICT_METHOD = arrayMethodIsStrict('sort'); + +var STABLE_SORT = !fails(function () { + // feature detection can be too slow, so check engines versions + if (V8) return V8 < 70; + if (FF && FF > 3) return; + if (IE_OR_EDGE) return true; + if (WEBKIT) return WEBKIT < 603; + + var result = ''; + var code, chr, value, index; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + test.push({ k: chr + index, v: value }); + } + } + + test.sort(function (a, b) { return b.v - a.v; }); + + for (index = 0; index < test.length; index++) { + chr = test[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + return result !== 'DGBEFHACIJK'; +}); + +var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; + +var getSortCompare = function (comparefn) { + return function (x, y) { + if (y === undefined) return -1; + if (x === undefined) return 1; + if (comparefn !== undefined) return +comparefn(x, y) || 0; + return String(x) > String(y) ? 1 : -1; + }; +}; // `Array.prototype.sort` method // https://tc39.es/ecma262/#sec-array.prototype.sort -$({ target: 'Array', proto: true, forced: [].sort !== sort }, { - sort: sort +$({ target: 'Array', proto: true, forced: FORCED }, { + sort: function sort(comparefn) { + if (comparefn !== undefined) aFunction(comparefn); + + var array = toObject(this); + + if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn); + + var items = []; + var arrayLength = toLength(array.length); + var itemsLength, index; + + for (index = 0; index < arrayLength; index++) { + if (index in array) items.push(array[index]); + } + + // TODO: use something more complex like timsort? + items = internalSort(items, getSortCompare(comparefn)); + itemsLength = items.length; + index = 0; + + while (index < itemsLength) array[index] = items[index++]; + while (index < arrayLength) delete array[index++]; + + return array; + } }); diff --git a/packages/core-js/modules/es.typed-array.sort.js b/packages/core-js/modules/es.typed-array.sort.js index 6f2b39784b82..b0cae2f68065 100644 --- a/packages/core-js/modules/es.typed-array.sort.js +++ b/packages/core-js/modules/es.typed-array.sort.js @@ -3,7 +3,8 @@ var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); var global = require('../internals/global'); var fails = require('../internals/fails'); var aFunction = require('../internals/a-function'); -var arraySort = require('../internals/array-sort'); +var toLength = require('../internals/to-length'); +var internalSort = require('../internals/array-sort'); var FF = require('../internals/engine-ff-version'); var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); var V8 = require('../internals/engine-v8-version'); @@ -47,10 +48,39 @@ var STABLE_SORT = !!nativeSort && !fails(function () { } }); +var getSortCompare = function (comparefn) { + return function (x, y) { + if (comparefn !== undefined) return +comparefn(x, y) || 0; + // eslint-disable-next-line no-self-compare -- NaN check + if (y !== y) return -1; + // eslint-disable-next-line no-self-compare -- NaN check + if (x !== x) return 1; + if (x === 0 && y === 0) return 1 / x > 0 && 1 / y < 0 ? 1 : -1; + return x > y; + }; +}; + // `%TypedArray%.prototype.sort` method // https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort exportTypedArrayMethod('sort', function sort(comparefn) { - if (!STABLE_SORT) return arraySort.call(aTypedArray(this), comparefn); + var array = this; if (comparefn !== undefined) aFunction(comparefn); - return nativeSort.call(this, comparefn); + if (STABLE_SORT) return nativeSort.call(array, comparefn); + + aTypedArray(array); + var arrayLength = toLength(array.length); + var items = Array(arrayLength); + var index; + + for (index = 0; index < arrayLength; index++) { + items[index] = array[index]; + } + + items = internalSort(array, getSortCompare(comparefn)); + + for (index = 0; index < arrayLength; index++) { + array[index] = items[index]; + } + + return array; }, !STABLE_SORT || ACCEPT_INCORRECT_ARGUMENTS); diff --git a/tests/pure/es.array.sort.js b/tests/pure/es.array.sort.js index a01e12878400..ac560ef946e0 100644 --- a/tests/pure/es.array.sort.js +++ b/tests/pure/es.array.sort.js @@ -80,6 +80,8 @@ QUnit.test('Array#sort', assert => { sort(array, (a, b) => (a / 4 | 0) - (b / 4 | 0)); + assert.ok(1 / sort([0, -0])[0] > 0, '-0'); + assert.same(String(array), String(expected), 'stable #1'); let result = ''; diff --git a/tests/tests/es.array.sort.js b/tests/tests/es.array.sort.js index 94aa6f30d72b..3ec67369a92d 100644 --- a/tests/tests/es.array.sort.js +++ b/tests/tests/es.array.sort.js @@ -80,6 +80,8 @@ QUnit.test('Array#sort', assert => { array.sort((a, b) => (a / 4 | 0) - (b / 4 | 0)); + assert.ok(1 / [0, -0].sort()[0] > 0, '-0'); + assert.same(String(array), String(expected), 'stable #1'); let result = ''; diff --git a/tests/tests/es.typed-array.sort.js b/tests/tests/es.typed-array.sort.js index 3182cf3c2966..2ab504c45cbc 100644 --- a/tests/tests/es.typed-array.sort.js +++ b/tests/tests/es.typed-array.sort.js @@ -10,6 +10,11 @@ if (DESCRIPTORS) QUnit.test('%TypedArrayPrototype%.sort', assert => { assert.name(sort, 'sort', `${ name }::sort name is 'sort'`); assert.looksNative(sort, `${ name }::sort looks native`); + if (name.indexOf('Float') === 0) { + assert.ok(1 / new TypedArray([0, -0]).sort()[0] < 0, '-0'); + assert.deepEqual(new TypedArray([NaN, 1, NaN]).sort(), new TypedArray([1, NaN, NaN]), 'NaN'); + } + if (name.indexOf('8') === -1) { const expected = Array(516); let array = new TypedArray(516); From fb75c0a937f69801637582636a233373049092cc Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 21:42:00 +0700 Subject: [PATCH 7/8] some improvements --- packages/core-js/internals/array-sort.js | 2 +- packages/core-js/modules/es.array.sort.js | 1 - tests/pure/es.array.sort.js | 1 + tests/tests/es.array.sort.js | 1 + tests/tests/es.typed-array.sort.js | 1 + 5 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core-js/internals/array-sort.js b/packages/core-js/internals/array-sort.js index c815d7ce3158..876c93296176 100644 --- a/packages/core-js/internals/array-sort.js +++ b/packages/core-js/internals/array-sort.js @@ -1,4 +1,4 @@ -'use strict'; +// TODO: use something more complex like timsort? var floor = Math.floor; var mergeSort = function (array, comparefn) { diff --git a/packages/core-js/modules/es.array.sort.js b/packages/core-js/modules/es.array.sort.js index 551a5a1bd8b6..ce7296a51380 100644 --- a/packages/core-js/modules/es.array.sort.js +++ b/packages/core-js/modules/es.array.sort.js @@ -89,7 +89,6 @@ $({ target: 'Array', proto: true, forced: FORCED }, { if (index in array) items.push(array[index]); } - // TODO: use something more complex like timsort? items = internalSort(items, getSortCompare(comparefn)); itemsLength = items.length; index = 0; diff --git a/tests/pure/es.array.sort.js b/tests/pure/es.array.sort.js index ac560ef946e0..e0dbf62f4e18 100644 --- a/tests/pure/es.array.sort.js +++ b/tests/pure/es.array.sort.js @@ -10,6 +10,7 @@ QUnit.test('Array#sort', assert => { assert.deepEqual(sort([1, 3, 2]), [1, 2, 3], '#1'); assert.deepEqual(sort([1, 3, 2, 11]), [1, 11, 2, 3], '#2'); + assert.deepEqual(sort([1, -1, 3, NaN, 2, 0, 11, -0]), [-1, 0, -0, 1, 11, 2, 3, NaN], '#3'); let array = Array(5); array[0] = 1; diff --git a/tests/tests/es.array.sort.js b/tests/tests/es.array.sort.js index 3ec67369a92d..f2195f0b5afe 100644 --- a/tests/tests/es.array.sort.js +++ b/tests/tests/es.array.sort.js @@ -10,6 +10,7 @@ QUnit.test('Array#sort', assert => { assert.deepEqual([1, 3, 2].sort(), [1, 2, 3], '#1'); assert.deepEqual([1, 3, 2, 11].sort(), [1, 11, 2, 3], '#2'); + assert.deepEqual([1, -1, 3, NaN, 2, 0, 11, -0].sort(), [-1, 0, -0, 1, 11, 2, 3, NaN], '#1'); let array = Array(5); array[0] = 1; diff --git a/tests/tests/es.typed-array.sort.js b/tests/tests/es.typed-array.sort.js index 2ab504c45cbc..d7088b6c0c87 100644 --- a/tests/tests/es.typed-array.sort.js +++ b/tests/tests/es.typed-array.sort.js @@ -11,6 +11,7 @@ if (DESCRIPTORS) QUnit.test('%TypedArrayPrototype%.sort', assert => { assert.looksNative(sort, `${ name }::sort looks native`); if (name.indexOf('Float') === 0) { + assert.deepEqual(new TypedArray([1, -1, 3, NaN, 2, 0, 11, -0]).sort(), new TypedArray([-1, -0, 0, 1, 2, 3, 11, NaN]), '#1'); assert.ok(1 / new TypedArray([0, -0]).sort()[0] < 0, '-0'); assert.deepEqual(new TypedArray([NaN, 1, NaN]).sort(), new TypedArray([1, NaN, NaN]), 'NaN'); } From a914d6515a469fc47272bb07db280321cf530c19 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 4 Jun 2021 22:48:56 +0700 Subject: [PATCH 8/8] fix a typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ba27df769f..9b7e4e9bfd89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Changelog ##### Unreleased - Added polyfill of stable sort in `{ Array, %TypedArray% }.prototype.sort`, [#769](https://github.com/zloirock/core-js/issues/769) -- Fixed `Safari` 14.0- `{ Array, %TypedArray% }.prototype.sort` validation of arguments bug +- Fixed `Safari` 14.0- `%TypedArray%.prototype.sort` validation of arguments bug - `.at` marked as supported from V8 9.2 ##### 3.13.1 - 2021.05.29