diff --git a/builtin_string.go b/builtin_string.go index 93ada478..5e35dc86 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -680,6 +680,44 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value { return stringReplace(s, found, str, rcall) } +func (r *Runtime) stringproto_replaceAll(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + searchValue := call.Argument(0) + replaceValue := call.Argument(1) + if searchValue != _undefined && searchValue != _null { + if isRegexp(searchValue) { + if o, ok := searchValue.(*Object); ok { + flags := nilSafe(o.self.getStr("flags", nil)) + r.checkObjectCoercible(flags) + if !strings.Contains(flags.toString().String(), "g") { + panic(r.NewTypeError("String.prototype.replaceAll called with a non-global RegExp argument")) + } + } + } + if replacer := toMethod(r.getV(searchValue, SymReplace)); replacer != nil { + return replacer(FunctionCall{ + This: searchValue, + Arguments: []Value{call.This, replaceValue}, + }) + } + } + + s := call.This.toString() + var found [][]int + searchStr := searchValue.toString() + searchLength := searchStr.Length() + advanceBy := toIntStrict(max(1, int64(searchLength))) + + pos := s.index(searchStr, 0) + for pos != -1 { + found = append(found, []int{pos, pos + searchLength}) + pos = s.index(searchStr, pos+advanceBy) + } + + str, rcall := getReplaceValue(replaceValue) + return stringReplace(s, found, str, rcall) +} + func (r *Runtime) stringproto_search(call FunctionCall) Value { r.checkObjectCoercible(call.This) regexp := call.Argument(0) @@ -976,6 +1014,7 @@ func (r *Runtime) initString() { o._putProp("padStart", r.newNativeFunc(r.stringproto_padStart, nil, "padStart", nil, 1), true, false, true) o._putProp("repeat", r.newNativeFunc(r.stringproto_repeat, nil, "repeat", nil, 1), true, false, true) o._putProp("replace", r.newNativeFunc(r.stringproto_replace, nil, "replace", nil, 2), true, false, true) + o._putProp("replaceAll", r.newNativeFunc(r.stringproto_replaceAll, nil, "replaceAll", nil, 2), true, false, true) o._putProp("search", r.newNativeFunc(r.stringproto_search, nil, "search", nil, 1), true, false, true) o._putProp("slice", r.newNativeFunc(r.stringproto_slice, nil, "slice", nil, 2), true, false, true) o._putProp("split", r.newNativeFunc(r.stringproto_split, nil, "split", nil, 2), true, false, true) diff --git a/string_ascii.go b/string_ascii.go index ec6180ef..0728f1e1 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -313,6 +313,9 @@ func (s asciiString) CompareTo(other String) int { func (s asciiString) index(substr String, start int) int { a, u := devirtualizeString(substr) if u == nil { + if start > len(s) { + return -1 + } p := strings.Index(string(s[start:]), string(a)) if p >= 0 { return p + start diff --git a/tc39_test.go b/tc39_test.go index acc499e3..ea9d2234 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -196,13 +196,15 @@ var ( // Character \ missing from character class [\c] "test/annexB/built-ins/RegExp/RegExp-invalid-control-escape-character-class.js": true, "test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true, + + // Skip due to regexp named groups + "test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": true, } featuresBlackList = []string{ "async-iteration", "Symbol.asyncIterator", "BigInt", - "String.prototype.replaceAll", "resizable-arraybuffer", "regexp-named-groups", "regexp-dotall",