-
Notifications
You must be signed in to change notification settings - Fork 17
How should replaceAll behave if searchValue is a non-global RegExp? #16
Comments
As much as I generally support consistency, the problem is that the two proposals use "All" for different concepts. In I can't think of a better naming convention. But it does mean that you'll no longer be able to polyfill your function with a simple split + join sequence, without some conditionals. (Why didn't I think of |
In |
If we follow |
I think that's a good end result, it also makes it very easy to implement. 😃 |
I agree with @AmeliaBR in that I'd prefer non-global regexps to be changed to global ones. I kind of read it as 'replace all occurrences that match this thing' (but the thing (a regex) only matches once if it isn't global, haha tricked you!). I also agree that the 'all' refers to a different thing. I also agree with @mathiasbynens that the point of this proposal was to add functionality where |
If |
Just like how |
+1 I actually tried to advocate for that the first time I presented this proposal (Nov 2017) and it was explicitly shut down. So yes, throwing for RegExp arguments is not an option for TC39. |
It seems there's some confusion about the 'All' in 'replaceAll' and 'matchAll'. That's unfortunate, and why I argued against the - IMHO unintuitive - name 'matchAll' back then. People have already said in this thread that 'All' refers to totally different concepts in these 2 methods. That's why I don't think it makes any sense to try and reach some sort of consistency between them. replaceAll is the global variant of replace. It finds and replaces all occurrences of searchValue with replaceValue. Just like what the 'replace all' button in any text editor does. Adding any non-global behavior there sounds pretty crazy to me ;)
+1.
I think what we are currently proposing (add a @@replaceall symbol, make all regexes global) is the only & simplest solution that is consistent with user expectations (that arise due to the similar naming as String.p.replace). |
Given the committee’s reaction to matchAll auto-g-ing regexes (its original behavior), i expect replaceAll would end up in the same place - where “all” applies to all the places matched, and a non-global regex matches 0 or 1 times. |
During offline discussions at the last TC39 meeting I've come back around on this; I like @schuay's example of "replace all" functionality in text editors; it would be very surprising if that'd only replace a single instance if "RegExp mode" is enabled. |
Text editors treat the input as a pattern, and implicitly auto-g for “find all” and “replace all” - but they typically have a non-global regex for “find” and “replace” - I’m not sure that comparison can really hold here. If the user has explicitly provided a non-global regex, then i think they’ve explicitly chosen non-all semantics. |
They already explicitly choose the "all" semantics by using |
While i agree with that philosophically, the committee explicitly chose to not go with “choosing all is an explicit choice for g” for matchAll, and i don’t see why replaceAll would be any different. |
Could we rename Then |
Given the non-analogy of this feature with I agree that |
"every" suggests |
Does anyone remember why we made |
@jridgewell that was my initial design; see here but mostly https://github.com/rwaldron/tc39-notes/blob/def2ee0c04bc91612576237314a4f3b1fe2edaef/meetings/2015-09/sept-23.md#512-proposal-stringmatchall - by adding the |
I agree for the consistency with And if the only diff of So I think there are only two reasonable solutions:
|
Based on the results of this Twitter poll, it looks like there is an overwhelming support for https://twitter.com/AmeliasBrain/status/1146496834275143681?s=19 (I'm massively in favour of auto-g being applied to both |
I agree, both should functions auto‑ |
The committee disagreed and matchAll shipped with the current semantic of obeying the regex it’s given - that ship, i think, has sailed. |
😢 It is still early days, it is possible that developers either aren't using it yet or they are only using it with the The only way updating this functionality will break a site is if the developer has used Basically what I'm saying is that I think it is unlikely that a developer has used A warning can be issued in the console if a non-global regex is being used saying that the functionality is changing and to use I know it isn't desirable but I have seen something handled in this way once before. I can't remember what it was for though 😖 |
For a consistent api - as in, right now you never need to use match, only matchAll. The console isn’t part of the language. |
That is a fair point. Code can be made cleaner by using No need to check if the global flag is enabled on the regex to switch between ...Is it even possible to check for a global flag on a regex? 🤔 |
@Dan503 yes, with |
In addition to the direct results to the poll, I've been reading all the comment replies. Some thoughts based on those: A number of people bring up @mathiasbynens's original idea that replaceAll should only apply to strings, & a RegExp should throw an error. (I'm sorry I didn't think to include this option in the poll itself!) At this point, I would prefer this over having There was also an interesting suggestion that, instead of a new method, this functionality could be achieved with a new options parameter to Based on all the discussion, my prefered order of outcomes are:
|
To me, “all” for a non global regex i expect to be a list of 1. |
@ljharb that's like a word in some prose having an asterisk/footnote on it. <joke>could we just rename it to |
It’s how querySelectorAll works when it only finds one match. |
That's entirely different. The analog here would be if you had to do |
A non-global regex can only have one possible matching element (for a given lastIndex value) - it's explicitly part of the contract of a non-global regex. |
In CSS, everything is matched, unless you go out of your way to restrict it. A selector like The default behavior in the absence of a What about flipping this around... having |
Imagined scenario:
Can you see why that would be confusing to users? When the users complain to the author of this text editor, they just respond, "Look, if you want it to replace all, you have to remember to check the checkbox. If you forget, you only get a single replacement. Doesn't matter what the feature name was that you invoked." It is a very hard sell to explain to learners: "The difference between |
Yes, i understand that regexes carrying the state for “global” creates this problem - editors (and some languages) don’t have such a flag usually, so they have to slash get to rely on other UI/API to do so. Unfortunately, in JS, the presence of the g flag would make any such UI redundant - which means that “one match” vs “all matches” would be a very confusing and inconsistent and redundant thing to be specified by API naming. That this means that the word “all” might confuse those who completely reasonably expect it to mean “all matches” is imo an unavoidable consequence of the g flag existing, and trying to half-“fix” that would make things worse. (matchAll isn’t really about all matches, which match already provides, it’s about all matches’ match objects, which match doesn’t provide when the regex is global). |
Regarding my earlier suggestion of While I disagree that renaming is inherently riskier than changing functionality (for precisely the reason that @hax gave above), it seems that this isn't a usable precedent here anyway. Going back to the notes, I had forgotten that it was the unpublishing of the Atomics feature as a Spectre/Meltdown mitigation that created a unique opportunity to rename a method that was already in the spec. Apologies for the ruckus. |
We were discussing this and I wanted to voice my opinion on this.
what's the expeted result? Edit: I really feel this should throw on any regex. As there is already the normal replace that can handle regex, this one isn't adding any value. And more importantly, it makes it harder for browser engines to optimize. Throwing only for non-global regex, and allowing global ones is eventually my 3rth preference, but again, this is making it even fuzzier for devs. allowing a normal regex to use it as a global really makes more sense. |
@SanderElias Agreed, I listed earlier the same top two "best options" as you, but mine were flipped in preference order. I can't see any other option being better (or even acceptable) than one of those two. EDIT: to clarify, for throwing an error, I think a non-global regex should throw, but a global regex should be accepted. |
It was claimed earlier in this thread that This is a naive, poorly performing approach, and maybe/probably I am missing some details (?), but... would something like this be possible? function replaceAll(str,re,...args) {
if (re.global) {
return str.replace(re,...args);
}
else {
let newStr = str;
let prevStr;
// repeatedly apply the one non-global replace RE
// against the string, until no more changes
// occur
do {
prevStr = newStr;
newStr = prevStr.replace(re,...args);
}
while (newStr != prevStr);
return newStr;
}
}
var str = "Hello World";
var re = /l/;
replaceAll(str,re,"L"); // HeLLo WorLd
replaceAll(str,re,x => x.toUpperCase()); // HeLLo WorLd |
What’s the lastIndex of |
AFAIK, |
I think it would on a sticky one, but fair point there. |
Ahh, forgot about stickies. var s = "abababab";
var re = /a./y;
replaceAll(s,re,x => { console.log(`lastIndex: ${re.lastIndex}`); return x.toUpperCase(); });
// lastIndex: 2
// lastIndex: 4
// lastIndex: 6
// lastIndex: 8
// "ABABABAB" To me, this seems expected/reasonable. But it would (unfortunately) diverge from how var s = "abababab";
var re = /a./yg;
s.replace(re,x => { console.log(`lastIndex: ${re.lastIndex}`); return x.toUpperCase(); });
// lastIndex: 0
// lastIndex: 0
// lastIndex: 0
// lastIndex: 0
// "ABABABAB" Is that divergence a deal breaker? Weighing all various downsides discussed so far, I wouldn't put that high on the list. Basically, we could just document that "All" in the case of non-global regexes means, "repeatedly applied individually until all possible replacements are complete. This is different, and may perform differently, than just passing a global-flagged regex." |
Upon further reflection, there's serious issues with my "repeatedly apply..." suggestion that are probably deal breakers. For example: var re = /./;
var str = "abc";
replaceAll(str,re,"x"); // will run forever, since `re` matches the replacement I think we would have to explore if the JS engine could somehow -- internally only -- update the effective Maybe it could like clone the original regex once, and actually use a temporary regex where it could update the lastIndex? Maybe the spec could change lastIndex to be an internal slot as well as a public property, that are normally updated at the same time, but in this special case, during the algorithm, update only the slot and not the property. I dunno. Just pondering possible ways to hack this. If there's absolutely no way to do anything like that, this idea is probably DOA, and I'd go back to saying this case should just throw an exception. [Edit]: Sorry for the explorative noise. Here's a much more complex polyfill that works around these sorts of problems. Ugly, but I think it works: https://gist.github.com/getify/f9df482f89ac8cdc17f339c81da74c3d |
@getify You seem to be documenting technical reasons why it is a good idea to throw on all regex ;) |
One more point I don’t see anyone mentioning. This implementation is future-compatible: 'aaa'.replaceAll(/a/, 'b') // exception thrown whereas this one is not: 'aaa'.replaceAll(/a/, 'b') // 'bbb' If we make |
Good discussion here! TypeDefs for /**
* Replaces text in a string, using a regular expression or search string.
* The original string is left unchanged.
* @param searchValue A RegExp object, literal, or string to search for.
* @param replaceValue The string that replaces the substring specified by the specified searchValue parameter. A number of special replacement patterns are supported.
* @returns A new string with some or all matches of a pattern replaced by a replacement.
*/
replace(searchValue: string | RegExp, replaceValue: string): string;
/**
* Replaces text in a string, using a regular expression or search string.
* @param searchValue A RegExp object, literal, or string to search for.
* @param replacer A function that returns the replacement text.
* @returns A new string with some or all matches of a pattern replaced by a replacement.
*/
replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string; I've read through this whole thread and my suggestion would be for I find the following to be clear and provide the desired convenience: /**
* Replaces all text matches, of a search string, in a string with the replacement string.
* The original string is left unchanged.
* @param searchValue A string to search for.
* @param replaceValue A string containing the text to replace for every successful match of searchValue in this string.
* @returns A new string with all matches of searchValue replaced by replaceValue.
*/
replaceAll(searchValue: string, replaceValue: string): string As noted above, I agree that it would be easier to change this to the following later if there was demand for it: replaceAll(searchValue: string | RegExp, replaceValue: string): string I also agree that |
Re-posting some valuable links that @ljharb dug up w.r.t. the
|
Per the July TC39 meeting consensus, we'd like to make the upcoming String.prototype.replaceAll proposal throw for non-global RegExp searchValues. However, String.prototype.matchAll currently does not throw in this case, causing consistency concerns. This patch adds a use counter for String.prototype.matchAll with a non-global RegExp as the searchValue. Hopefully, this pattern isn't too common in real-world code today, in which case we can both a) change matchAll and b) proceed with the desired replaceAll semantics. tc39/proposal-string-replaceall#16 V8 CL: https://chromium-review.googlesource.com/c/v8/v8/+/1718145 Chromium CL: https://chromium-review.googlesource.com/c/chromium/src/+/1718367 BUG=v8:9551 Change-Id: Ica660a0a6189d84c3d33398c98305d0bcb9f8c23 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1718145 Commit-Queue: Mathias Bynens <[email protected]> Reviewed-by: Ulan Degenbaev <[email protected]> Reviewed-by: Jakob Gruber <[email protected]> Cr-Commit-Position: refs/heads/master@{#62913}
Per the July TC39 meeting consensus, we'd like to make the upcoming String.prototype.replaceAll proposal throw for non-global RegExp searchValues. However, String.prototype.matchAll currently does not throw in this case, causing consistency concerns. This patch adds a use counter for String.prototype.matchAll with a non-global RegExp as the searchValue. Hopefully, this pattern isn't too common in real-world code today, in which case we can both a) change matchAll and b) proceed with the desired replaceAll semantics. tc39/proposal-string-replaceall#16 V8 CL: https://chromium-review.googlesource.com/c/v8/v8/+/1718145 Chromium CL: https://chromium-review.googlesource.com/c/chromium/src/+/1718367 BUG=v8:9551 Change-Id: I6b4895e87bb498c0284d2852f1d9f0cafbc087d6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1718367 Commit-Queue: Mathias Bynens <[email protected]> Reviewed-by: Michael Lippautz <[email protected]> Cr-Commit-Position: refs/heads/master@{#681918}
We're going with the solution in #24: |
Not sure if you're still looking for feedback here, but I support somehow making sure that replaceAll does not act non-globally. Either implicitly adding the |
@littledan I'm not sure if you missed the previous comment or I'm misinterpreting you, but that's already happened: They decided to choose the "throwing an exception" option. |
@Zarel I'm expressing agreement with that comment. I was asked to give feedback on the Stage 3 review issue. |
Related to, but different from #8. #8 wants it to auto-convert a non-global regex to a global regex. I think we should accept regex
searchValue
s, but not convert them to a global regex.String.p.matchAll
is setting precedent for "all" methods:The text was updated successfully, but these errors were encountered: