From 35a2ba8ed8796d8218fa8ea549c113a1191c604b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 7 Jul 2021 15:18:20 -0400 Subject: [PATCH] LibJS: Implement RegExp.prototype [ @@search ] String.prototype.search is already implemented, but relies on the well- known Symbol.search, which was not implemented. --- .../LibJS/Runtime/RegExpPrototype.cpp | 49 +++++++++++++++++++ .../Libraries/LibJS/Runtime/RegExpPrototype.h | 1 + .../String/String.prototype.search.js | 47 ++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.search.js diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp index 70e74c83c2..38f700922f 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -32,6 +32,7 @@ void RegExpPrototype::initialize(GlobalObject& global_object) define_native_function(*vm.well_known_symbol_match(), symbol_match, 1, attr); define_native_function(*vm.well_known_symbol_replace(), symbol_replace, 2, attr); + define_native_function(*vm.well_known_symbol_search(), symbol_search, 1, attr); define_native_accessor(vm.names.flags, flags, {}, Attribute::Configurable); define_native_accessor(vm.names.source, source, {}, Attribute::Configurable); @@ -541,4 +542,52 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace) return js_string(vm, builder.build()); } +// 22.2.5.11 RegExp.prototype [ @@search ] ( string ), https://tc39.es/ecma262/#sec-regexp.prototype-@@search +JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_search) +{ + auto string_value = vm.argument(0); + + auto* regexp_object = this_object_from(vm, global_object); + if (!regexp_object) + return {}; + auto string = string_value.to_string(global_object); + if (vm.exception()) + return {}; + + auto previous_last_index = regexp_object->get(vm.names.lastIndex); + if (vm.exception()) + return {}; + if (!same_value(previous_last_index, Value(0))) { + regexp_object->set(vm.names.lastIndex, Value(0), true); + if (vm.exception()) + return {}; + } + + auto result = regexp_exec(global_object, *regexp_object, string); + if (vm.exception()) + return {}; + + auto current_last_index = regexp_object->get(vm.names.lastIndex); + if (vm.exception()) + return {}; + if (!same_value(current_last_index, previous_last_index)) { + regexp_object->set(vm.names.lastIndex, previous_last_index, true); + if (vm.exception()) + return {}; + } + + if (result.is_null()) + return Value(-1); + + auto* result_object = result.to_object(global_object); + if (!result_object) + return {}; + + auto index = result_object->get(vm.names.index); + if (vm.exception()) + return {}; + + return index; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h index faae712b16..2353c37eb8 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h @@ -27,6 +27,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(symbol_match); JS_DECLARE_NATIVE_FUNCTION(symbol_replace); + JS_DECLARE_NATIVE_FUNCTION(symbol_search); #define __JS_ENUMERATE(_, flag_name, ...) \ JS_DECLARE_NATIVE_GETTER(flag_name); diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.search.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.search.js new file mode 100644 index 0000000000..8419a3cf1e --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.search.js @@ -0,0 +1,47 @@ +test("basic functionality", () => { + expect(String.prototype.search).toHaveLength(1); + + expect("hello friends".search("h")).toBe(0); + expect("hello friends".search("e")).toBe(1); + expect("hello friends".search("l")).toBe(2); + expect("hello friends".search("o")).toBe(4); + expect("hello friends".search("z")).toBe(-1); + + expect("abc123def".search(/\d/)).toBe(3); + expect("abcdef".search(/\d/)).toBe(-1); +}); + +test("override exec with function", () => { + let calls = 0; + + let re = /test/; + let oldExec = re.exec.bind(re); + re.exec = function (...args) { + ++calls; + return oldExec(...args); + }; + + expect("test".search(re)).toBe(0); + expect(calls).toBe(1); +}); + +test("override exec with bad function", () => { + let calls = 0; + + let re = /test/; + re.exec = function (...args) { + ++calls; + return 4; + }; + + expect(() => { + "test".search(re); + }).toThrow(TypeError); + expect(calls).toBe(1); +}); + +test("override exec with non-function", () => { + let re = /test/; + re.exec = 3; + expect("test".search(re)).toBe(0); +});