mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 12:07:45 +00:00
LibJS: Implement most of String.prototype.replaceAll
This also renames ErrorType::StringMatchAllNonGlobalRegExp to ErrorType::StringNonGlobalRegExp (removes "MatchAll") because this error is now used in the same way from multiple operations.
This commit is contained in:
parent
cb20baebae
commit
9f0aef6051
5 changed files with 190 additions and 2 deletions
|
@ -94,6 +94,7 @@
|
||||||
__JS_ENUMERATE(match, match) \
|
__JS_ENUMERATE(match, match) \
|
||||||
__JS_ENUMERATE(matchAll, match_all) \
|
__JS_ENUMERATE(matchAll, match_all) \
|
||||||
__JS_ENUMERATE(replace, replace) \
|
__JS_ENUMERATE(replace, replace) \
|
||||||
|
__JS_ENUMERATE(replaceAll, replace_all) \
|
||||||
__JS_ENUMERATE(search, search) \
|
__JS_ENUMERATE(search, search) \
|
||||||
__JS_ENUMERATE(split, split) \
|
__JS_ENUMERATE(split, split) \
|
||||||
__JS_ENUMERATE(hasInstance, has_instance) \
|
__JS_ENUMERATE(hasInstance, has_instance) \
|
||||||
|
|
|
@ -157,7 +157,7 @@
|
||||||
"not be accessed in strict mode") \
|
"not be accessed in strict mode") \
|
||||||
M(SpeciesConstructorDidNotCreate, "Species constructor did not create {}") \
|
M(SpeciesConstructorDidNotCreate, "Species constructor did not create {}") \
|
||||||
M(SpeciesConstructorReturned, "Species constructor returned {}") \
|
M(SpeciesConstructorReturned, "Species constructor returned {}") \
|
||||||
M(StringMatchAllNonGlobalRegExp, "RegExp argument is non-global") \
|
M(StringNonGlobalRegExp, "RegExp argument is non-global") \
|
||||||
M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \
|
M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \
|
||||||
M(StringRepeatCountMustBe, "repeat count must be a {} number") \
|
M(StringRepeatCountMustBe, "repeat count must be a {} number") \
|
||||||
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
||||||
|
|
|
@ -82,6 +82,7 @@ void StringPrototype::initialize(GlobalObject& global_object)
|
||||||
define_native_function(vm.names.match, match, 1, attr);
|
define_native_function(vm.names.match, match, 1, attr);
|
||||||
define_native_function(vm.names.matchAll, match_all, 1, attr);
|
define_native_function(vm.names.matchAll, match_all, 1, attr);
|
||||||
define_native_function(vm.names.replace, replace, 2, attr);
|
define_native_function(vm.names.replace, replace, 2, attr);
|
||||||
|
define_native_function(vm.names.replaceAll, replace_all, 2, attr);
|
||||||
define_native_function(vm.names.search, search, 1, attr);
|
define_native_function(vm.names.search, search, 1, attr);
|
||||||
define_native_function(vm.names.anchor, anchor, 1, attr);
|
define_native_function(vm.names.anchor, anchor, 1, attr);
|
||||||
define_native_function(vm.names.big, big, 0, attr);
|
define_native_function(vm.names.big, big, 0, attr);
|
||||||
|
@ -765,7 +766,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match_all)
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
if (!flags_string.contains("g")) {
|
if (!flags_string.contains("g")) {
|
||||||
vm.throw_exception<TypeError>(global_object, ErrorType::StringMatchAllNonGlobalRegExp);
|
vm.throw_exception<TypeError>(global_object, ErrorType::StringNonGlobalRegExp);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -835,6 +836,90 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
|
||||||
return js_string(vm, builder.build());
|
return js_string(vm, builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 22.1.3.18 String.prototype.replaceAll ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replaceall
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
|
||||||
|
{
|
||||||
|
auto this_object = require_object_coercible(global_object, vm.this_value(global_object));
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
auto search_value = vm.argument(0);
|
||||||
|
auto replace_value = vm.argument(1);
|
||||||
|
|
||||||
|
if (!search_value.is_nullish()) {
|
||||||
|
bool is_regexp = search_value.is_regexp(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (is_regexp) {
|
||||||
|
auto flags = search_value.as_object().get(vm.names.flags);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
auto flags_object = require_object_coercible(global_object, flags);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
auto flags_string = flags_object.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
if (!flags_string.contains("g")) {
|
||||||
|
vm.throw_exception<TypeError>(global_object, ErrorType::StringNonGlobalRegExp);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* replacer = search_value.get_method(global_object, *vm.well_known_symbol_replace());
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
if (replacer) {
|
||||||
|
auto result = vm.call(*replacer, search_value, this_object, replace_value);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto string = this_object.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
auto search_string = search_value.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
Vector<size_t> match_positions = string.find_all(search_string);
|
||||||
|
size_t end_of_last_match = 0;
|
||||||
|
|
||||||
|
StringBuilder result;
|
||||||
|
|
||||||
|
for (auto position : match_positions) {
|
||||||
|
auto preserved = string.substring_view(end_of_last_match, position - end_of_last_match);
|
||||||
|
String replacement;
|
||||||
|
|
||||||
|
if (replace_value.is_function()) {
|
||||||
|
auto result = vm.call(replace_value.as_function(), js_undefined(), search_value, Value(position), js_string(vm, string));
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
replacement = result.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
// FIXME: Implement the GetSubstituion algorithm for substituting placeholder '$' characters - https://tc39.es/ecma262/#sec-getsubstitution
|
||||||
|
replacement = replace_value.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(preserved);
|
||||||
|
result.append(replacement);
|
||||||
|
|
||||||
|
end_of_last_match = position + search_string.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end_of_last_match < string.length())
|
||||||
|
result.append(string.substring_view(end_of_last_match));
|
||||||
|
|
||||||
|
return js_string(vm, result.build());
|
||||||
|
}
|
||||||
|
|
||||||
// 22.1.3.19 String.prototype.search ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.search
|
// 22.1.3.19 String.prototype.search ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.search
|
||||||
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::search)
|
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::search)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,6 +46,7 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(match);
|
JS_DECLARE_NATIVE_FUNCTION(match);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(match_all);
|
JS_DECLARE_NATIVE_FUNCTION(match_all);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(replace);
|
JS_DECLARE_NATIVE_FUNCTION(replace);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(replace_all);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(search);
|
JS_DECLARE_NATIVE_FUNCTION(search);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(anchor);
|
JS_DECLARE_NATIVE_FUNCTION(anchor);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(big);
|
JS_DECLARE_NATIVE_FUNCTION(big);
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
test("invariants", () => {
|
||||||
|
expect(String.prototype.replaceAll).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("error cases", () => {
|
||||||
|
[null, undefined].forEach(value => {
|
||||||
|
expect(() => {
|
||||||
|
value.replace("", "");
|
||||||
|
}).toThrow(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
"".replaceAll(/abc/, "");
|
||||||
|
}).toThrow(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("basic string replacement", () => {
|
||||||
|
expect("".replaceAll("", "")).toBe("");
|
||||||
|
expect("".replaceAll("a", "")).toBe("");
|
||||||
|
expect("".replaceAll("", "a")).toBe("a");
|
||||||
|
expect("abc".replaceAll("", "x")).toBe("xaxbxcx");
|
||||||
|
|
||||||
|
expect("a".replaceAll("a", "")).toBe("");
|
||||||
|
expect("a".replaceAll("a", "b")).toBe("b");
|
||||||
|
expect("aa".replaceAll("a", "b")).toBe("bb");
|
||||||
|
expect("ca".replaceAll("a", "b")).toBe("cb");
|
||||||
|
expect("aca".replaceAll("a", "b")).toBe("bcb");
|
||||||
|
expect("aca".replaceAll("ca", "b")).toBe("ab");
|
||||||
|
expect("aca".replaceAll("ac", "b")).toBe("ba");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("convertible string replacement", () => {
|
||||||
|
expect("1223".replaceAll(2, "x")).toBe("1xx3");
|
||||||
|
expect("1223".replaceAll("2", 4)).toBe("1443");
|
||||||
|
expect("1223".replaceAll(2, 4)).toBe("1443");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("functional string replacement", () => {
|
||||||
|
expect(
|
||||||
|
"aba".replaceAll("a", function () {
|
||||||
|
return "c";
|
||||||
|
})
|
||||||
|
).toBe("cbc");
|
||||||
|
expect("aba".replaceAll("a", () => "c")).toBe("cbc");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
"aba".replaceAll("a", (search, position, string) => {
|
||||||
|
expect(search).toBe("a");
|
||||||
|
expect(position <= 2).toBeTrue();
|
||||||
|
expect(string).toBe("aba");
|
||||||
|
return "x";
|
||||||
|
})
|
||||||
|
).toBe("xbx");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("basic regex replacement", () => {
|
||||||
|
expect("".replaceAll(/a/g, "")).toBe("");
|
||||||
|
expect("a".replaceAll(/a/g, "")).toBe("");
|
||||||
|
|
||||||
|
expect("abc123def".replaceAll(/\D/g, "*")).toBe("***123***");
|
||||||
|
expect("123abc456".replaceAll(/\D/g, "*")).toBe("123***456");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("functional regex replacement", () => {
|
||||||
|
expect(
|
||||||
|
"a".replace(/a/g, function () {
|
||||||
|
return "b";
|
||||||
|
})
|
||||||
|
).toBe("b");
|
||||||
|
expect("a".replace(/a/g, () => "b")).toBe("b");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
"abc".replace(/\D/g, (matched, position, string) => {
|
||||||
|
expect(matched).toBe(string[position]);
|
||||||
|
expect(position <= 2).toBeTrue();
|
||||||
|
expect(string).toBe("abc");
|
||||||
|
return "x";
|
||||||
|
})
|
||||||
|
).toBe("xxx");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
"abc".replace(/(\D)/g, (matched, capture1, position, string) => {
|
||||||
|
expect(matched).toBe(string[position]);
|
||||||
|
expect(capture1).toBe(string[position]);
|
||||||
|
expect(position <= 2).toBeTrue();
|
||||||
|
expect(string).toBe("abc");
|
||||||
|
return "x";
|
||||||
|
})
|
||||||
|
).toBe("xxx");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
"abcd".replace(/(\D)b(\D)/g, (matched, capture1, capture2, position, string) => {
|
||||||
|
expect(matched).toBe("abc");
|
||||||
|
expect(capture1).toBe("a");
|
||||||
|
expect(capture2).toBe("c");
|
||||||
|
expect(position).toBe(0);
|
||||||
|
expect(string).toBe("abcd");
|
||||||
|
return "x";
|
||||||
|
})
|
||||||
|
).toBe("xd");
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue