diff --git a/README.md b/README.md index d219e8721171..6de9199635ce 100644 --- a/README.md +++ b/README.md @@ -1664,6 +1664,26 @@ Promise.allSettled([ Promise.resolve(3), ]).then(console.log); // => [{ value: 1, status: 'fulfilled' }, { reason: 2, status: 'rejected' }, { value: 3, status: 'fulfilled' }] ``` +* `String#replaceAll` [proposal](https://github.com/tc39/proposal-string-replace-all) - modules [`esnext.string.replace-all`](https://github.com/zloirock/core-js/blob/v3.0.1/packages/core-js/modules/esnext.string.replace-all.js) and [`esnext.symbol.replace-all`](https://github.com/zloirock/core-js/blob/v3.0.1/packages/core-js/modules/esnext.symbol.replace-all.js). +```js +class String { + replaceAll(searchValue: string | RegExp, replaceString: string): string; +} + +class Symbol { + static replaceAll: @@replaceAll; +} +``` +[*CommonJS entry points:*](#commonjs-api) +```js +core-js/proposals/string-replace-all +core-js/features/string/replace-all +core-js/features/symbol/replace-all +``` +[*Examples*](https://goo.gl/wUXNXN): +```js +'Test abc test test abc test.'.replaceAll('abc', 'foo'); // -> 'Test foo test test foo test.' +``` #### Stage 1 proposals [*CommonJS entry points:*](#commonjs-api) @@ -1693,21 +1713,6 @@ array.lastItem = 4; array; // => [1, 2, 4] ``` -* `String#replaceAll` [proposal](https://github.com/tc39/proposal-string-replace-all) - module [`esnext.string.replace-all`](https://github.com/zloirock/core-js/blob/v3.0.1/packages/core-js/modules/esnext.string.replace-all.js) -```js -class String { - replaceAll(searchValue: string | RegExp, replaceString: string): string; -} -``` -[*CommonJS entry points:*](#commonjs-api) -```js -core-js/proposals/string-replace-all -core-js/features/string/replace-all -``` -[*Examples*](https://goo.gl/wUXNXN): -```js -'Test abc test test abc test.'.replaceAll('abc', 'foo'); // -> 'Test foo test test foo test.' -``` * `Promise.try` [proposal](https://github.com/tc39/proposal-promise-try) - module [`esnext.promise.try`](https://github.com/zloirock/core-js/blob/v3.0.1/packages/core-js/modules/esnext.promise.try.js) ```js class Promise { diff --git a/packages/core-js-compat/src/data.js b/packages/core-js-compat/src/data.js index 0f056c95f661..c25252e363b8 100644 --- a/packages/core-js-compat/src/data.js +++ b/packages/core-js-compat/src/data.js @@ -1328,6 +1328,8 @@ module.exports = { }, 'esnext.symbol.pattern-match': { }, + 'esnext.symbol.replace-all': { + }, 'esnext.weak-map.delete-all': { }, 'esnext.weak-map.from': { diff --git a/packages/core-js-compat/src/modules-by-versions.js b/packages/core-js-compat/src/modules-by-versions.js index da8888a79d71..11e175130934 100644 --- a/packages/core-js-compat/src/modules-by-versions.js +++ b/packages/core-js-compat/src/modules-by-versions.js @@ -1,4 +1,6 @@ 'use strict'; module.exports = { - // '3.1': ['esnext.builtin.feature'], + 3.1: [ + 'esnext.symbol.replace-all', + ], }; diff --git a/packages/core-js/features/symbol/index.js b/packages/core-js/features/symbol/index.js index bdaac9b462a8..578691cd71a3 100644 --- a/packages/core-js/features/symbol/index.js +++ b/packages/core-js/features/symbol/index.js @@ -3,3 +3,4 @@ module.exports = require('../../es/symbol'); require('../../modules/esnext.symbol.dispose'); require('../../modules/esnext.symbol.observable'); require('../../modules/esnext.symbol.pattern-match'); +require('../../modules/esnext.symbol.replace-all'); diff --git a/packages/core-js/features/symbol/replace-all.js b/packages/core-js/features/symbol/replace-all.js new file mode 100644 index 000000000000..9d24d37ae902 --- /dev/null +++ b/packages/core-js/features/symbol/replace-all.js @@ -0,0 +1,3 @@ +require('../../modules/esnext.symbol.replace-all'); + +module.exports = require('../../internals/wrapped-well-known-symbol').f('replaceAll'); diff --git a/packages/core-js/modules/esnext.string.replace-all.js b/packages/core-js/modules/esnext.string.replace-all.js index a6e114eb80ce..235247fe0e76 100644 --- a/packages/core-js/modules/esnext.string.replace-all.js +++ b/packages/core-js/modules/esnext.string.replace-all.js @@ -1,22 +1,50 @@ 'use strict'; +var hide = require('../internals/hide'); var requireObjectCoercible = require('../internals/require-object-coercible'); +var anObject = require('../internals/an-object'); var isRegExp = require('../internals/is-regexp'); var getRegExpFlags = require('../internals/regexp-flags'); var speciesConstructor = require('../internals/species-constructor'); +var REPLACE_ALL = require('../internals/well-known-symbol')('replaceAll'); +var IS_PURE = require('../internals/is-pure'); +var RegExpPrototype = RegExp.prototype; + +var $replaceAll = function (string, replaceValue) { + var rx = anObject(this); + var flags = String('flags' in RegExpPrototype ? rx.flags : getRegExpFlags.call(rx)); + if (!~flags.indexOf('g')) { + rx = new (speciesConstructor(rx, RegExp))(rx.source, flags + 'g'); + } + return String(string).replace(rx, replaceValue); +}; // `String.prototype.replaceAll` method // https://github.com/tc39/proposal-string-replace-all require('../internals/export')({ target: 'String', proto: true }, { replaceAll: function replaceAll(searchValue, replaceValue) { var O = requireObjectCoercible(this); - var search, flags; - if (isRegExp(searchValue)) { - flags = getRegExpFlags.call(searchValue); - if (!~flags.indexOf('g')) { - search = new (speciesConstructor(searchValue, RegExp))(searchValue.source, flags + 'g'); - } else search = searchValue; - return String(O).replace(search, replaceValue); + var replacer, string, searchString, template, result, i; + if (searchValue != null) { + replacer = searchValue[REPLACE_ALL]; + if (replacer !== undefined) { + return replacer.call(searchValue, O, replaceValue); + } else if (IS_PURE && isRegExp(searchValue)) { + return $replaceAll.call(searchValue, O, replaceValue); + } + } + string = String(O); + searchString = String(searchValue); + template = string.split(searchString); + if (typeof replaceValue !== 'function') { + return template.join(String(replaceValue)); } - return String(O).split(searchValue).join(replaceValue); + result = template[0]; + for (i = 1; i < template.length; i++) { + result += String(replaceValue(searchString, i - 1, string)); + result += template[i]; + } + return result; } }); + +IS_PURE || REPLACE_ALL in RegExpPrototype || hide(RegExpPrototype, REPLACE_ALL, $replaceAll); diff --git a/packages/core-js/modules/esnext.symbol.replace-all.js b/packages/core-js/modules/esnext.symbol.replace-all.js new file mode 100644 index 000000000000..ed80d7049e29 --- /dev/null +++ b/packages/core-js/modules/esnext.symbol.replace-all.js @@ -0,0 +1,3 @@ +// `Symbol.replaceAll` well-known symbol +// https://tc39.github.io/proposal-string-replaceall/ +require('../internals/define-well-known-symbol')('replaceAll'); diff --git a/packages/core-js/proposals/string-replace-all.js b/packages/core-js/proposals/string-replace-all.js index d5d30341fd0f..c36697d72242 100644 --- a/packages/core-js/proposals/string-replace-all.js +++ b/packages/core-js/proposals/string-replace-all.js @@ -1 +1,2 @@ require('../modules/esnext.string.replace-all'); +require('../modules/esnext.symbol.replace-all'); diff --git a/packages/core-js/stage/1.js b/packages/core-js/stage/1.js index 4296e5d849ee..907dc5dbf3eb 100644 --- a/packages/core-js/stage/1.js +++ b/packages/core-js/stage/1.js @@ -10,7 +10,6 @@ require('../proposals/promise-try'); require('../proposals/keys-composition'); require('../proposals/seeded-random'); require('../proposals/string-code-points'); -require('../proposals/string-replace-all'); require('../proposals/using-statement'); module.exports = require('./2'); diff --git a/packages/core-js/stage/2.js b/packages/core-js/stage/2.js index a97dbc26a60f..2f2fb1f3e1ea 100644 --- a/packages/core-js/stage/2.js +++ b/packages/core-js/stage/2.js @@ -1,4 +1,5 @@ require('../proposals/set-methods'); require('../proposals/promise-all-settled'); +require('../proposals/string-replace-all'); module.exports = require('./3'); diff --git a/tests/commonjs.js b/tests/commonjs.js index 1db452e05392..7a9cd013e988 100644 --- a/tests/commonjs.js +++ b/tests/commonjs.js @@ -285,6 +285,7 @@ for (const _PATH of ['../packages/core-js-pure', '../packages/core-js']) { ok(load('features/symbol/async-iterator')); ok(load('features/symbol/observable')); ok(load('features/symbol/pattern-match')); + ok(load('features/symbol/replace-all')); ok(load('features/symbol/dispose')); ok(typeof load('features/symbol/for') === 'function'); ok(typeof load('features/symbol/key-for') === 'function'); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index d01e5e104969..deb2b7d69588 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1227,6 +1227,9 @@ GLOBAL.tests = { 'esnext.symbol.pattern-match': function () { return Symbol.patternMatch; }, + 'esnext.symbol.replace-all': function () { + return Symbol.replaceAll; + }, 'esnext.weak-map.delete-all': function () { return WeakMap.prototype.deleteAll; }, diff --git a/tests/pure/esnext.string.replace-all.js b/tests/pure/esnext.string.replace-all.js index 92710ac3d2e9..a597c9b2f88b 100644 --- a/tests/pure/esnext.string.replace-all.js +++ b/tests/pure/esnext.string.replace-all.js @@ -1,6 +1,7 @@ import { STRICT } from '../helpers/constants'; import replaceAll from 'core-js-pure/features/string/replace-all'; +import Symbol from 'core-js-pure/features/symbol'; QUnit.test('String#replaceAll', assert => { assert.isFunction(replaceAll); @@ -8,6 +9,22 @@ QUnit.test('String#replaceAll', assert => { assert.same(replaceAll('foo', 'o', {}), 'f[object Object][object Object]'); assert.same(replaceAll('[object Object]x[object Object]', {}, 'y'), 'yxy'); assert.same(replaceAll({}, 'bject', 'lolo'), '[ololo Ololo]'); + assert.same(replaceAll('aba', 'b', (search, i, string) => { + assert.same(search, 'b', '`search` is `b`'); + assert.same(i, 0, '`i` is 0'); + assert.same(string, 'aba', '`string` is `aba`'); + return 'c'; + }), 'aca'); + const searcher = { + [Symbol.replaceAll](O, replaceValue) { + assert.same(this, searcher, '`this` is `searcher`'); + assert.same(String(O), 'aba', '`O` is `aba`'); + assert.same(String(replaceValue), 'c', '`replaceValue` is `c`'); + return 'foo'; + }, + }; + assert.same(replaceAll('aba', searcher, 'c'), 'foo'); + assert.same(replaceAll('aba', 'b'), 'aundefineda'); if (STRICT) { assert.throws(() => replaceAll(null, 'a', 'b'), TypeError); assert.throws(() => replaceAll(undefined, 'a', 'b'), TypeError); diff --git a/tests/pure/esnext.symbol.replace-all.js b/tests/pure/esnext.symbol.replace-all.js new file mode 100644 index 000000000000..85996dafad77 --- /dev/null +++ b/tests/pure/esnext.symbol.replace-all.js @@ -0,0 +1,6 @@ +import Symbol from 'core-js-pure/features/symbol'; + +QUnit.test('Symbol.replaceAll', assert => { + assert.ok('replaceAll' in Symbol, 'Symbol.replaceAll is available'); + assert.ok(Object(Symbol.replaceAll) instanceof Symbol, 'Symbol.replaceAll is symbol'); +}); diff --git a/tests/pure/index.js b/tests/pure/index.js index 01bd7dcdb793..6b5c8e4abf21 100644 --- a/tests/pure/index.js +++ b/tests/pure/index.js @@ -205,8 +205,10 @@ import './esnext.set.union'; import './esnext.string.at'; import './esnext.string.code-points'; import './esnext.string.match-all'; +import './esnext.symbol.dispose'; import './esnext.symbol.observable'; import './esnext.symbol.pattern-match'; +import './esnext.symbol.replace-all'; import './esnext.weak-map.delete-all'; import './esnext.weak-map.from'; import './esnext.weak-map.of'; diff --git a/tests/tests/esnext.string.replace-all.js b/tests/tests/esnext.string.replace-all.js index e8d1dbfa0bec..91f9612d7f54 100644 --- a/tests/tests/esnext.string.replace-all.js +++ b/tests/tests/esnext.string.replace-all.js @@ -11,6 +11,22 @@ QUnit.test('String#replaceAll', assert => { assert.same('foo'.replaceAll('o', {}), 'f[object Object][object Object]'); assert.same('[object Object]x[object Object]'.replaceAll({}, 'y'), 'yxy'); assert.same(replaceAll.call({}, 'bject', 'lolo'), '[ololo Ololo]'); + assert.same('aba'.replaceAll('b', (search, i, string) => { + assert.same(search, 'b', '`search` is `b`'); + assert.same(i, 0, '`i` is 0'); + assert.same(string, 'aba', '`string` is `aba`'); + return 'c'; + }), 'aca'); + const searcher = { + [Symbol.replaceAll](O, replaceValue) { + assert.same(this, searcher, '`this` is `searcher`'); + assert.same(String(O), 'aba', '`O` is `aba`'); + assert.same(String(replaceValue), 'c', '`replaceValue` is `c`'); + return 'foo'; + }, + }; + assert.same('aba'.replaceAll(searcher, 'c'), 'foo'); + assert.same('aba'.replaceAll('b'), 'aundefineda'); if (STRICT) { assert.throws(() => replaceAll.call(null, 'a', 'b'), TypeError); assert.throws(() => replaceAll.call(undefined, 'a', 'b'), TypeError); diff --git a/tests/tests/esnext.symbol.replace-all.js b/tests/tests/esnext.symbol.replace-all.js new file mode 100644 index 000000000000..d4ab590d89e3 --- /dev/null +++ b/tests/tests/esnext.symbol.replace-all.js @@ -0,0 +1,13 @@ +import { DESCRIPTORS } from '../helpers/constants'; + +QUnit.test('Symbol.replaceAll', assert => { + assert.ok('replaceAll' in Symbol, 'Symbol.replaceAll is available'); + assert.nonEnumerable(Symbol, 'replaceAll'); + assert.ok(Object(Symbol.replaceAll) instanceof Symbol, 'Symbol.replaceAll is symbol'); + if (DESCRIPTORS) { + const descriptor = Object.getOwnPropertyDescriptor(Symbol, 'replaceAll'); + assert.ok(!descriptor.enumerble, 'non-enumerable'); + assert.ok(!descriptor.writable, 'non-writable'); + assert.ok(!descriptor.configurable, 'non-configurable'); + } +}); diff --git a/tests/tests/index.js b/tests/tests/index.js index 5fabb11224e1..4fbdc6c4a220 100644 --- a/tests/tests/index.js +++ b/tests/tests/index.js @@ -260,6 +260,7 @@ import './esnext.string.replace-all'; import './esnext.symbol.dispose'; import './esnext.symbol.observable'; import './esnext.symbol.pattern-match'; +import './esnext.symbol.replace-all'; import './esnext.weak-map.delete-all'; import './esnext.weak-map.from'; import './esnext.weak-map.of';