From 3ee5217adc32ed438215bc0f6938396318024b24 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 1 Dec 2022 10:52:10 -0500 Subject: [PATCH] LibJS: Implement String.prototype.toWellFormed --- .../LibJS/Runtime/CommonPropertyNames.h | 1 + .../LibJS/Runtime/StringPrototype.cpp | 41 ++++++++++++++++ .../Libraries/LibJS/Runtime/StringPrototype.h | 1 + .../String/String.prototype.toWellFormed.js | 47 +++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toWellFormed.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 9064a8522e..8012c68bd1 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -526,6 +526,7 @@ namespace JS { P(toTimeString) \ P(toUpperCase) \ P(toUTCString) \ + P(toWellFormed) \ P(toZonedDateTime) \ P(toZonedDateTimeISO) \ P(trace) \ diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index 80cfd0d27b..e4bc3f0f44 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -171,6 +171,7 @@ void StringPrototype::initialize(Realm& realm) define_native_function(realm, vm.names.toLowerCase, to_lowercase, 0, attr); define_native_function(realm, vm.names.toString, to_string, 0, attr); define_native_function(realm, vm.names.toUpperCase, to_uppercase, 0, attr); + define_native_function(realm, vm.names.toWellFormed, to_well_formed, 0, attr); define_native_function(realm, vm.names.trim, trim, 0, attr); define_native_function(realm, vm.names.trimEnd, trim_end, 0, attr); define_native_function(realm, vm.names.trimStart, trim_start, 0, attr); @@ -980,6 +981,46 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase) return js_string(vm, move(uppercase)); } +// 22.1.3.11 String.prototype.toWellFormed ( ) +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_well_formed) +{ + // 1. Let O be ? RequireObjectCoercible(this value). + // 2. Let S be ? ToString(O). + auto string = TRY(utf16_string_from(vm)); + + // 3. Let strLen be the length of S. + auto length = string.length_in_code_units(); + + // 4. Let k be 0. + size_t k = 0; + + // 5. Let result be the empty String. + StringBuilder result; + + // 6. Repeat, while k < strLen, + while (k < length) { + // a. Let cp be CodePointAt(S, k). + auto code_point = JS::code_point_at(string.view(), k); + + // b. If cp.[[IsUnpairedSurrogate]] is true, then + if (code_point.is_unpaired_surrogate) { + // i. Set result to the string-concatenation of result and 0xFFFD (REPLACEMENT CHARACTER). + result.append_code_point(0xfffd); + } + // c. Else, + else { + // i. Set result to the string-concatenation of result and UTF16EncodeCodePoint(cp.[[CodePoint]]). + result.append_code_point(code_point.code_point); + } + + // d. Set k to k + cp.[[CodeUnitCount]]. + k += code_point.code_unit_count; + } + + // 7. Return result. + return js_string(vm, result.build()); +} + ThrowCompletionOr trim_string(VM& vm, Value input_value, TrimMode where) { // 1. Let str be ? RequireObjectCoercible(string). diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.h b/Userland/Libraries/LibJS/Runtime/StringPrototype.h index 2d51769e04..76670eb3a8 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.h @@ -59,6 +59,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(to_lowercase); JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(to_uppercase); + JS_DECLARE_NATIVE_FUNCTION(to_well_formed); JS_DECLARE_NATIVE_FUNCTION(trim); JS_DECLARE_NATIVE_FUNCTION(trim_end); JS_DECLARE_NATIVE_FUNCTION(trim_start); diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toWellFormed.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toWellFormed.js new file mode 100644 index 0000000000..8948c981ad --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toWellFormed.js @@ -0,0 +1,47 @@ +describe("errors", () => { + test("called with value that cannot be converted to a string", () => { + expect(() => { + String.prototype.toWellFormed.call(Symbol.hasInstance); + }).toThrowWithMessage(TypeError, "Cannot convert symbol to string"); + }); +}); + +describe("basic functionality", () => { + test("ascii strings", () => { + expect("".toWellFormed()).toBe(""); + expect("foo".toWellFormed()).toBe("foo"); + expect("abcdefghi".toWellFormed()).toBe("abcdefghi"); + }); + + test("valid UTF-16 strings", () => { + expect("😀".toWellFormed()).toBe("😀"); + expect("\ud83d\ude00".toWellFormed()).toBe("\ud83d\ude00"); + }); + + test("invalid UTF-16 strings", () => { + expect("😀".slice(0, 1).toWellFormed()).toBe("\ufffd"); + expect("😀".slice(1, 2).toWellFormed()).toBe("\ufffd"); + expect("\ud83d".toWellFormed()).toBe("\ufffd"); + expect("\ude00".toWellFormed()).toBe("\ufffd"); + expect("a\ud83d".toWellFormed()).toBe("a\ufffd"); + expect("a\ude00".toWellFormed()).toBe("a\ufffd"); + expect("\ud83da".toWellFormed()).toBe("\ufffda"); + expect("\ude00a".toWellFormed()).toBe("\ufffda"); + expect("a\ud83da".toWellFormed()).toBe("a\ufffda"); + expect("a\ude00a".toWellFormed()).toBe("a\ufffda"); + }); + + test("object converted to a string", () => { + let toStringCalled = false; + + const obj = { + toString: function () { + toStringCalled = true; + return "toString"; + }, + }; + + expect(String.prototype.toWellFormed.call(obj)).toBe("toString"); + expect(toStringCalled).toBeTrue(); + }); +});