From 2299be474bda4071238f3a4eca061f5447bef228 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Wed, 16 Jun 2021 11:36:23 +0300 Subject: [PATCH] LibJS: Add the String.fromCodePoint() method --- .../LibJS/Runtime/CommonPropertyNames.h | 1 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../LibJS/Runtime/StringConstructor.cpp | 23 ++++++++++++ .../LibJS/Runtime/StringConstructor.h | 1 + .../builtins/String/String.fromCodePoint.js | 37 +++++++++++++++++++ 5 files changed, 63 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/String/String.fromCodePoint.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 869ddb5de0..1c97d1c43c 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -128,6 +128,7 @@ namespace JS { P(freeze) \ P(from) \ P(fromCharCode) \ + P(fromCodePoint) \ P(fromEntries) \ P(fround) \ P(gc) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 2d6057d47f..62f6f08f10 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -31,6 +31,7 @@ M(InOperatorWithObject, "'in' operator must be used on an object") \ M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object") \ M(InvalidAssignToConst, "Invalid assignment to const variable") \ + M(InvalidCodePoint, "Invalid code point {}, must be an integer no less than 0 and no greater than 0x10FFFF") \ M(InvalidHint, "Invalid hint: \"{}\"") \ M(InvalidIndex, "Index must be a positive integer") \ M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \ diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp index ed23df20bc..834fe49140 100644 --- a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp @@ -32,6 +32,7 @@ void StringConstructor::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.raw, raw, 1, attr); define_native_function(vm.names.fromCharCode, from_char_code, 1, attr); + define_native_function(vm.names.fromCodePoint, from_code_point, 1, attr); } StringConstructor::~StringConstructor() @@ -120,4 +121,26 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_char_code) return js_string(vm, builder.build()); } +// 22.1.2.2 String.fromCodePoint ( ...codePoints ), https://tc39.es/ecma262/#sec-string.fromcodepoint +JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_code_point) +{ + StringBuilder builder; + for (size_t i = 0; i < vm.argument_count(); ++i) { + auto next_code_point = vm.argument(i).to_number(global_object); + if (vm.exception()) + return {}; + if (!next_code_point.is_integer()) { + vm.throw_exception(global_object, ErrorType::InvalidCodePoint, next_code_point.to_string_without_side_effects()); + return {}; + } + auto code_point = next_code_point.to_i32(global_object); + if (code_point < 0 || code_point > 0x10FFFF) { + vm.throw_exception(global_object, ErrorType::InvalidCodePoint, next_code_point.to_string_without_side_effects()); + return {}; + } + builder.append_code_point(code_point); + } + return js_string(vm, builder.build()); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.h b/Userland/Libraries/LibJS/Runtime/StringConstructor.h index c6fb72cd29..7b660540ba 100644 --- a/Userland/Libraries/LibJS/Runtime/StringConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.h @@ -26,6 +26,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(raw); JS_DECLARE_NATIVE_FUNCTION(from_char_code); + JS_DECLARE_NATIVE_FUNCTION(from_code_point); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.fromCodePoint.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.fromCodePoint.js new file mode 100644 index 0000000000..220655d8be --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.fromCodePoint.js @@ -0,0 +1,37 @@ +test("basic functionality", () => { + expect(String.fromCodePoint).toHaveLength(1); + + expect(String.fromCodePoint()).toBe(""); + expect(String.fromCodePoint(0)).toBe("\u0000"); + expect(String.fromCodePoint(false)).toBe("\u0000"); + expect(String.fromCodePoint(null)).toBe("\u0000"); + expect(String.fromCodePoint(1)).toBe("\u0001"); + expect(String.fromCodePoint(true)).toBe("\u0001"); + expect(String.fromCodePoint(0xffff)).toBe("\uffff"); + expect(String.fromCodePoint(65)).toBe("A"); + expect(String.fromCodePoint(65, 66, 67)).toBe("ABC"); + expect(String.fromCodePoint(228, 246, 252)).toBe("äöü"); +}); + +test("errors", () => { + expect(() => { + String.fromCodePoint(NaN); + }).toThrowWithMessage( + RangeError, + "must be an integer no less than 0 and no greater than 0x10FFFF" + ); + + expect(() => { + String.fromCodePoint(-5); + }).toThrowWithMessage( + RangeError, + "must be an integer no less than 0 and no greater than 0x10FFFF" + ); + + expect(() => { + String.fromCodePoint(0x123ffff); + }).toThrowWithMessage( + RangeError, + "must be an integer no less than 0 and no greater than 0x10FFFF" + ); +});