├── .editorconfig ├── .gitignore ├── .nvmrc ├── .travis-github-deploy-key.enc ├── .travis.yml ├── README.md ├── package.json └── spec.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{README.md,package.json,spec.html,.travis.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | 3 | dist 4 | 5 | # Installed npm modules 6 | node_modules 7 | 8 | # Folder view configuration files 9 | .DS_Store 10 | Desktop.ini 11 | 12 | # Thumbnail cache files 13 | ._* 14 | Thumbs.db 15 | 16 | # Files that might appear on external disks 17 | .Spotlight-V100 18 | .Trashes 19 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /.travis-github-deploy-key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tc39/proposal-string-replaceall/c64a7e62e3288a8cd4f7a4e3f1145545b47f9e7b/.travis-github-deploy-key.enc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | after_success: 3 | - $(npm bin)/set-up-ssh --key "${encrypted_57be495982e6_key}" --iv "${encrypted_57be495982e6_iv}" 4 | --path-encrypted-key .travis-github-deploy-key.enc 5 | - $(npm bin)/update-branch --commands 'npm run build' --commit-message "Update gh-pages 6 | @ ${TRAVIS_COMMIT}" --directory 'dist' --distribution-branch 'gh-pages' --source-branch 7 | 'master' 8 | branches: 9 | only: 10 | - master 11 | env: 12 | global: 13 | secure: DR+3vbfdRzwV8aoaV0TRnc9a9uR+W/T40gUkrIvaZdDAfz1KRxeeZ7txdATtBG9AHdSOqiTr88uM5zbmbA9W00IyoL2DVa2KrVQNwKLdIcWxr3wiGsM6tnplbepoAe2/zpPtgKYtiHNBAqpyCg1k4bNUjRs/omT7uIPcYDdJU14UnoWq0+fBy+OrOvO1lShx4Loy1c8vqP2DEBAGUXMENVWBMH9eTd+zzneSXt9IWmuIBgisqpHBB4fX+EFyBbIHA/vJVolGjKIka2xX76Ixbef1KBOQf3LfyDuNWpZEKrKyShG42SuTxFjtVfxnvotQsWi/u72u59nHnb/MIi0IjRkx4yKl74hk4QEA/sC2s4bfMRw/ZXoHVEYwDCAZgkeyfSNGr6e4pzxkZ/KHhMcxNKQE2GdSK0V/1SbxeAgJoM5eTSoam4LMPhDCyGT7pIs999dZcYG4hqBVA8zb62emto0Giwwxi7IE5Qb3qWZQEUGREYxCMH6t1nMkmNx1pAIz9AN+itInobaTp15eptSHrvJ/W6JmrwfYYHzc2G3R/lwl/7AmzKnsqvkSngL6Pipj+7Hb4edMDCJ4WUZ88+udEBVwxKPDxbXgjOiOTr5cQWB+Bun5fufLVy+vLM7UGo8JvXo2JtgAOLxP1ev6jWhVgnVUEV8lWoiIEygRcG1wAFs= 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `String.prototype.replaceAll` proposal 2 | 3 | ## Status 4 | 5 | Champion: Mathias Bynens (Google, @mathiasbynens). 6 | 7 | This proposal is at stage 4 of [the TC39 process](https://tc39.es/process-document/), and is scheduled to be included in ES2021. 8 | 9 | ## Motivation 10 | 11 | (Also see [our TL;DR explainer](https://v8.dev/features/string-replaceall).) 12 | 13 | Currently there is no way to replace all instances of a substring in a string without use of a global regexp. 14 | `String.prototype.replace` only affects the first occurrence when used with a string argument. There is a lot of evidence that developers are trying to do this in JS — see the [StackOverflow question](https://stackoverflow.com/q/1144783/96656) with thousands of votes. 15 | 16 | Currently the most common way of achieving this is to use a global regexp. 17 | 18 | ```js 19 | const queryString = 'q=query+string+parameters'; 20 | const withSpaces = queryString.replace(/\+/g, ' '); 21 | ``` 22 | 23 | This approach has the downside of requiring special RegExp characters to be escaped — note the escaped `'+'`. 24 | 25 | An alternate solution is to combine `String#split` with `Array#join`: 26 | 27 | ```js 28 | const queryString = 'q=query+string+parameters'; 29 | const withSpaces = queryString.split('+').join(' '); 30 | ``` 31 | 32 | This approach avoids any escaping but comes with the overhead of splitting the string into an array of parts only to glue it back together. 33 | 34 | ## Proposed solution 35 | 36 | We propose the addition of a new method to the String prototype - `replaceAll`. This would give developers a straight-forward way to accomplish this common, basic operation. 37 | 38 | ```js 39 | const queryString = 'q=query+string+parameters'; 40 | const withSpaces = queryString.replaceAll('+', ' '); 41 | ``` 42 | 43 | It also removes the need to escape special regexp characters (note the unescaped `'+'`). 44 | 45 | ## High-level API 46 | 47 | The proposed signature is the same as the existing `String.prototype.replace` method: 48 | 49 | ```js 50 | String.prototype.replaceAll(searchValue, replaceValue) 51 | ``` 52 | 53 | Per the current TC39 consensus, `String.prototype.replaceAll` behaves identically to `String.prototype.replace` in all cases, **except** for the following two cases: 54 | 55 | 1. If `searchValue` is a string, `String.prototype.replace` only replaces a single occurrence of the `searchValue`, whereas `String.prototype.replaceAll` replaces *all* occurrences of the `searchValue` (as if `.split(searchValue).join(replaceValue)` or a global & properly-escaped regular expression had been used). 56 | 1. If `searchValue` is a non-global regular expression, `String.prototype.replace` replaces a single match, whereas `String.prototype.replaceAll` throws an exception. This is done to avoid the inherent confusion between the lack of a global flag (which implies "do NOT replace all") and the name of the method being called (which strongly suggests "replace all"). 57 | 58 | Notably, `String.prototype.replaceAll` behaves just like `String.prototype.replace` if `searchValue` is a global regular expression. 59 | 60 | ## Comparison to other languages 61 | 62 | * Java has [`replace`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#replace-java.lang.CharSequence-java.lang.CharSequence-), accepting a `CharSequence` and replacing all occurrences. Java also has [`replaceAll`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#replaceAll-java.lang.String-java.lang.String-) which accepts a regex as the search term (requiring the user to escape special regex characters), again replacing all occurrences by default. 63 | * Python [`replace`](https://www.tutorialspoint.com/python/string_replace.htm) replaces all occurrences, but accepts an optional param to limit the number of replacements. 64 | * PHP has [`str_replace`](http://php.net/manual/en/function.str-replace.php) which has an optional limit parameter like python. 65 | * Ruby has [`gsub`](https://ruby-doc.org/core/String.html#method-i-gsub), accepting a regexp or string, but also accepts a callback block and a hash of match -> replacement pairs. 66 | 67 | ## FAQ 68 | 69 | ### What are the main benefits? 70 | 71 | A simplified API for this common use-case that does not require RegExp knowledge. A way to global-replace strings without having to escape RegExp syntax characters. Possibly improved optimization potential on the VM side. 72 | 73 | ### What about adding a `limit` parameter to `replace` instead? 74 | 75 | A: This is an awkward interface — because the default limit is 1, the user would have to know how many occurrences already exist, or use something like Infinity. 76 | 77 | ### What happens if `searchValue` is the empty string? 78 | 79 | `String.prototype.replaceAll` follows the precedent set by `String.prototype.replace`, and returns the input string with the replacement value spliced in between every UCS-2/UTF-16 code unit. 80 | 81 | ```js 82 | 'x'.replace('', '_'); 83 | // → '_x' 84 | 'xxx'.replace(/(?:)/g, '_'); 85 | // → '_x_x_x_' 86 | 'xxx'.replaceAll('', '_'); 87 | // → '_x_x_x_' 88 | ``` 89 | 90 | ## TC39 meeting notes 91 | 92 | - [November 2017](https://tc39.es/tc39-notes/2017-11_nov-28.html#10ih-stringprototypereplaceall-for-stage-1) 93 | - [March 2019](https://github.com/tc39/tc39-notes/blob/master/meetings/2019-03/mar-26.md#stringprototypereplaceall-for-stage-2) 94 | - [July 2019](https://github.com/tc39/tc39-notes/blob/master/meetings/2019-07/july-24.md#stringprototypereplaceall) 95 | - [June 2020](https://github.com/tc39/notes/blob/master/meetings/2020-06/june-2.md#stringprototypereplaceall-for-stage-4) 96 | 97 | ## Specification 98 | 99 | - [Ecmarkup source](https://github.com/tc39/proposal-string-replaceall/blob/master/spec.html) 100 | - [HTML version](https://tc39.es/proposal-string-replaceall/) 101 | 102 | ## Implementations 103 | 104 | - [SpiderMonkey](https://bugzilla.mozilla.org/show_bug.cgi?id=1540021), shipping in [Firefox 77](https://bugzilla.mozilla.org/show_bug.cgi?id=1608168) 105 | - [JavaScriptCore](https://bugs.webkit.org/show_bug.cgi?id=202471), shipping in [Safari 13.1](https://webkit.org/blog/10247/new-webkit-features-in-safari-13-1/#javascript-improvements) 106 | - [V8](https://bugs.chromium.org/p/v8/issues/detail?id=9801), shipping in Chrome 85 107 | - Polyfills: 108 | - [core-js](https://github.com/zloirock/core-js#stringreplaceall) 109 | - [es-shims](https://github.com/es-shims/String.prototype.replaceAll) 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": ":", 5 | "build": "mkdir -p dist; ecmarkup --verbose spec.html dist/index.html --css dist/ecmarkup.css --js dist/ecmarkup.js" 6 | }, 7 | "devDependencies": { 8 | "@alrra/travis-scripts": "^3.0.1", 9 | "ecmarkup": "^3.16.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | title: String.prototype.replaceAll 5 | status: proposal 6 | stage: 4 7 | location: https://tc39.es/proposal-string-replace-all/ 8 | copyright: false 9 | contributors: Jakob Gruber, Mathias Bynens 10 |11 | 12 | 13 | 25 | 26 |
The abstract operation StringIndexOf takes arguments _string_ (a String), _searchValue_ (a String), and _fromIndex_ (a non-negative integer). It performs the following steps when called:
29 |If _searchValue_ is empty and _fromIndex_ is less than or equal to the length of _string_, this algorithm returns _fromIndex_. An empty _searchValue_ is effectively found at every position within a string, including after the last code unit.
41 |The above algorithm accepts out-of-bounds _fromIndex_ values (where _fromIndex_ ≥ the length of _string_).
44 |When the `replaceAll` method is called with arguments _searchValue_ and _replaceValue_, the following steps are taken:
50 |Performs a regular expression match of the String representing the *this* value against _regexp_ and returns an iterator. Each iteration result's value is an Array object containing the results of the match, or *null* if the String did not match.
97 |When the `matchAll` method is called, the following steps are taken:
98 |If _searchString_ appears as a substring of the result of converting this object to a String, at one or more indices that are greater than or equal to _position_, then the smallest such index is returned; otherwise, -1 is returned. If _position_ is *undefined*, 0 is assumed, so as to search all of the String.
121 |The `indexOf` method takes two arguments, _searchString_ and _position_, and performs the following steps:
123 |The `indexOf` function is intentionally generic; it does not require that its *this* value be a String object. Therefore, it can be transferred to other kinds of objects for use as a method.
138 |