diff options
5 files changed, 88 insertions, 0 deletions
diff --git a/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Libraries/LibJS/Runtime/CommonPropertyNames.h index aa3d1b8ea6..c9a31bd467 100644 --- a/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -93,6 +93,7 @@ namespace JS { P(description) \ P(done) \ P(dotAll) \ + P(endsWith) \ P(entries) \ P(enumerable) \ P(error) \ diff --git a/Libraries/LibJS/Runtime/StringPrototype.cpp b/Libraries/LibJS/Runtime/StringPrototype.cpp index bcfcb074cb..2799bf79aa 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -75,6 +75,7 @@ void StringPrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.charCodeAt, char_code_at, 1, attr); define_native_function(vm.names.repeat, repeat, 1, attr); define_native_function(vm.names.startsWith, starts_with, 1, attr); + define_native_function(vm.names.endsWith, ends_with, 1, attr); define_native_function(vm.names.indexOf, index_of, 1, attr); define_native_function(vm.names.toLowerCase, to_lowercase, 0, attr); define_native_function(vm.names.toUpperCase, to_uppercase, 0, attr); @@ -185,6 +186,51 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with) return Value(string.substring(start, search_string_length) == search_string); } +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::ends_with) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + + auto search_string_value = vm.argument(0); + + bool search_is_regexp = search_string_value.is_regexp(global_object); + if (vm.exception()) + return {}; + if (search_is_regexp) { + vm.throw_exception<TypeError>(global_object, ErrorType::IsNotA, "searchString", "string, but a regular expression"); + return {}; + } + + auto search_string = search_string_value.to_string(global_object); + if (vm.exception()) + return {}; + + auto string_length = string.length(); + auto search_string_length = search_string.length(); + + size_t pos = string_length; + + auto end_position_value = vm.argument(1); + if (!end_position_value.is_undefined()) { + auto number = end_position_value.to_number(global_object); + if (vm.exception()) + return {}; + double pos_as_double = number.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + pos = clamp(pos_as_double, static_cast<double>(0), static_cast<double>(string_length)); + } + + if (search_string_length == 0) + return Value(true); + if (pos < search_string_length) + return Value(false); + + auto start = pos - search_string_length; + return Value(string.substring(start, search_string_length) == search_string); +} + JS_DEFINE_NATIVE_FUNCTION(StringPrototype::index_of) { auto string = ak_string_from(vm, global_object); diff --git a/Libraries/LibJS/Runtime/StringPrototype.h b/Libraries/LibJS/Runtime/StringPrototype.h index e412b713ef..f443f73c2d 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.h +++ b/Libraries/LibJS/Runtime/StringPrototype.h @@ -43,6 +43,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(char_code_at); JS_DECLARE_NATIVE_FUNCTION(repeat); JS_DECLARE_NATIVE_FUNCTION(starts_with); + JS_DECLARE_NATIVE_FUNCTION(ends_with); JS_DECLARE_NATIVE_FUNCTION(index_of); JS_DECLARE_NATIVE_FUNCTION(to_lowercase); JS_DECLARE_NATIVE_FUNCTION(to_uppercase); diff --git a/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js b/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js index f5c0ffa854..a05ca53d8c 100644 --- a/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js +++ b/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js @@ -4,6 +4,7 @@ test("basic functionality", () => { "charCodeAt", "repeat", "startsWith", + "endsWith", "indexOf", "toLowerCase", "toUpperCase", diff --git a/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js b/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js new file mode 100644 index 0000000000..ef39541ce1 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js @@ -0,0 +1,39 @@ +test("basic functionality", () => { + expect(String.prototype.endsWith).toHaveLength(1); + + var s = "foobar"; + expect(s.endsWith("r")).toBeTrue(); + expect(s.endsWith("ar")).toBeTrue(); + expect(s.endsWith("bar")).toBeTrue(); + expect(s.endsWith("obar")).toBeTrue(); + expect(s.endsWith("oobar")).toBeTrue(); + expect(s.endsWith("foobar")).toBeTrue(); + expect(s.endsWith("1foobar")).toBeFalse(); + expect(s.endsWith("r", 6)).toBeTrue(); + expect(s.endsWith("ar", 6)).toBeTrue(); + expect(s.endsWith("bar", 6)).toBeTrue(); + expect(s.endsWith("obar", 6)).toBeTrue(); + expect(s.endsWith("oobar", 6)).toBeTrue(); + expect(s.endsWith("foobar", 6)).toBeTrue(); + expect(s.endsWith("1foobar", 6)).toBeFalse(); + expect(s.endsWith("bar", [])).toBeFalse(); + expect(s.endsWith("bar", null)).toBeFalse(); + expect(s.endsWith("bar", false)).toBeFalse(); + expect(s.endsWith("bar", true)).toBeFalse(); + expect(s.endsWith("f", true)).toBeTrue(); + expect(s.endsWith("bar", -1)).toBeFalse(); + expect(s.endsWith("bar", 42)).toBeTrue(); + expect(s.endsWith("foo", 3)).toBeTrue(); + expect(s.endsWith("foo", "3")).toBeTrue(); + expect(s.endsWith("foo1", 3)).toBeFalse(); + expect(s.endsWith("foo", 3.7)).toBeTrue(); + expect(s.endsWith()).toBeFalse(); + expect(s.endsWith("")).toBeTrue(); + expect(s.endsWith("", 0)).toBeTrue(); + expect(s.endsWith("", 1)).toBeTrue(); + expect(s.endsWith("", -1)).toBeTrue(); + expect(s.endsWith("", 42)).toBeTrue(); + expect("12undefined".endsWith()).toBeTrue(); + expect(() => s.endsWith(/foobar/)).toThrowWithMessage(TypeError, "searchString is not a string, but a regular expression"); + expect(s.endsWith("bar", undefined)).toBeTrue(); +}); |