From 4a3e142d554bcf93a720c70e9f817cc79700926f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 6 Sep 2021 22:18:48 -0400 Subject: [PATCH] LibJS: Implement a nearly empty Intl.Collator object This adds plumbing for the Intl.Collator object, constructor, and prototype. --- Userland/Libraries/LibJS/CMakeLists.txt | 3 + Userland/Libraries/LibJS/Forward.h | 1 + .../Libraries/LibJS/Runtime/GlobalObject.cpp | 2 + .../Libraries/LibJS/Runtime/Intl/Collator.cpp | 95 +++++++++++++++++++ .../Libraries/LibJS/Runtime/Intl/Collator.h | 82 ++++++++++++++++ .../Runtime/Intl/CollatorConstructor.cpp | 56 +++++++++++ .../LibJS/Runtime/Intl/CollatorConstructor.h | 28 ++++++ .../LibJS/Runtime/Intl/CollatorPrototype.cpp | 28 ++++++ .../LibJS/Runtime/Intl/CollatorPrototype.h | 23 +++++ .../Libraries/LibJS/Runtime/Intl/Intl.cpp | 2 + .../Intl/Collator/Collator.@@toStringTag.js | 3 + .../Tests/builtins/Intl/Collator/Collator.js | 5 + 12 files changed, 328 insertions(+) create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/Collator.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/Collator.h create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.@@toStringTag.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index a7bf6d0ba8..befb3c7d8b 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -88,6 +88,9 @@ set(SOURCES Runtime/GlobalObject.cpp Runtime/IndexedProperties.cpp Runtime/Intl/AbstractOperations.cpp + Runtime/Intl/Collator.cpp + Runtime/Intl/CollatorConstructor.cpp + Runtime/Intl/CollatorPrototype.cpp Runtime/Intl/DateTimeFormat.cpp Runtime/Intl/DateTimeFormatConstructor.cpp Runtime/Intl/DateTimeFormatFunction.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 3b21d3e548..0b3a144ec3 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -68,6 +68,7 @@ __JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double) #define JS_ENUMERATE_INTL_OBJECTS \ + __JS_ENUMERATE(Collator, collator, CollatorPrototype, CollatorConstructor) \ __JS_ENUMERATE(DateTimeFormat, date_time_format, DateTimeFormatPrototype, DateTimeFormatConstructor) \ __JS_ENUMERATE(DisplayNames, display_names, DisplayNamesPrototype, DisplayNamesConstructor) \ __JS_ENUMERATE(ListFormat, list_format, ListFormatPrototype, ListFormatConstructor) \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 34452aa99b..6f3bdd7479 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -50,6 +50,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Collator.cpp b/Userland/Libraries/LibJS/Runtime/Intl/Collator.cpp new file mode 100644 index 0000000000..88a4d38fab --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/Collator.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS::Intl { + +// 10 Collator Objects, https://tc39.es/ecma402/#collator-objects +Collator::Collator(Object& prototype) + : Object(prototype) +{ +} + +void Collator::set_usage(StringView type) +{ + if (type == "sort"sv) + m_usage = Usage::Sort; + else if (type == "search"sv) + m_usage = Usage::Search; + else + VERIFY_NOT_REACHED(); +} + +StringView Collator::usage_string() const +{ + switch (m_usage) { + case Usage::Sort: + return "sort"sv; + case Usage::Search: + return "search"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void Collator::set_sensitivity(StringView type) +{ + if (type == "base"sv) + m_sensitivity = Sensitivity::Base; + else if (type == "accent"sv) + m_sensitivity = Sensitivity::Accent; + else if (type == "case"sv) + m_sensitivity = Sensitivity::Case; + else if (type == "variant"sv) + m_sensitivity = Sensitivity::Variant; + else + VERIFY_NOT_REACHED(); +} + +StringView Collator::sensitivity_string() const +{ + switch (m_sensitivity) { + case Sensitivity::Base: + return "base"sv; + case Sensitivity::Accent: + return "accent"sv; + case Sensitivity::Case: + return "case"sv; + case Sensitivity::Variant: + return "variant"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void Collator::set_case_first(StringView case_first) +{ + if (case_first == "upper"sv) + m_case_first = CaseFirst::Upper; + else if (case_first == "lower"sv) + m_case_first = CaseFirst::Lower; + else if (case_first == "false"sv) + m_case_first = CaseFirst::False; + else + VERIFY_NOT_REACHED(); +} + +StringView Collator::case_first_string() const +{ + switch (m_case_first) { + case CaseFirst::Upper: + return "upper"sv; + case CaseFirst::Lower: + return "lower"sv; + case CaseFirst::False: + return "false"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Collator.h b/Userland/Libraries/LibJS/Runtime/Intl/Collator.h new file mode 100644 index 0000000000..3a7536874a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/Collator.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS::Intl { + +class Collator final : public Object { + JS_OBJECT(Collator, Object); + +public: + enum class Usage { + Sort, + Search, + }; + + enum class Sensitivity { + Base, + Accent, + Case, + Variant, + }; + + enum class CaseFirst { + Upper, + Lower, + False, + }; + + static constexpr auto relevant_extension_keys() + { + // 10.2.3 Internal Slots, https://tc39.es/ecma402/#sec-intl-collator-internal-slots + // The value of the [[RelevantExtensionKeys]] internal slot is a List that must include the element "co", may include any or all of the elements "kf" and "kn", and must not include any other elements. + return AK::Array { "co"sv, "kf"sv, "kn"sv }; + } + + explicit Collator(Object& prototype); + virtual ~Collator() override = default; + + String const& locale() const { return m_locale; } + void set_locale(String locale) { m_locale = move(locale); } + + Usage usage() const { return m_usage; } + void set_usage(StringView usage); + StringView usage_string() const; + + Sensitivity sensitivity() const { return m_sensitivity; } + void set_sensitivity(StringView sensitivity); + StringView sensitivity_string() const; + + CaseFirst case_first() const { return m_case_first; } + void set_case_first(StringView case_first); + StringView case_first_string() const; + + String const& collation() const { return m_collation; } + void set_collation(String collation) { m_collation = move(collation); } + + bool ignore_punctuation() const { return m_ignore_punctuation; } + void set_ignore_punctuation(bool ignore_punctuation) { m_ignore_punctuation = ignore_punctuation; } + + bool numeric() const { return m_numeric; } + void set_numeric(bool numeric) { m_numeric = numeric; } + +private: + String m_locale; // [[Locale]] + Usage m_usage { Usage::Sort }; // [[Usage]] + Sensitivity m_sensitivity { Sensitivity::Variant }; // [[Sensitivity]] + CaseFirst m_case_first { CaseFirst::False }; // [[CaseFirst]] + String m_collation; // [[Collation]] + bool m_ignore_punctuation { false }; // [[IgnorePunctuation]] + bool m_numeric { false }; // [[Numeric]] +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp new file mode 100644 index 0000000000..15d6015a84 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS::Intl { + +// 10.1 The Intl.Collator Constructor, https://tc39.es/ecma402/#sec-the-intl-collator-constructor +CollatorConstructor::CollatorConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Collator.as_string(), *global_object.function_prototype()) +{ +} + +void CollatorConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 10.2.1 Intl.Collator.prototype, https://tc39.es/ecma402/#sec-intl.collator.prototype + define_direct_property(vm.names.prototype, global_object.intl_collator_prototype(), 0); + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); +} + +// 10.1.2 Intl.Collator ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.collator +ThrowCompletionOr CollatorConstructor::call() +{ + // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget + return TRY(construct(*this)); +} + +// 10.1.2 Intl.Collator ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.collator +ThrowCompletionOr CollatorConstructor::construct(FunctionObject& new_target) +{ + auto& global_object = this->global_object(); + + // 2. Let internalSlotsList be « [[InitializedCollator]], [[Locale]], [[Usage]], [[Sensitivity]], [[IgnorePunctuation]], [[Collation]], [[BoundCompare]] ». + // 3. If %Collator%.[[RelevantExtensionKeys]] contains "kn", then + // a. Append [[Numeric]] as the last element of internalSlotsList. + // 4. If %Collator%.[[RelevantExtensionKeys]] contains "kf", then + // a. Append [[CaseFirst]] as the last element of internalSlotsList. + + // 5. Let collator be ? OrdinaryCreateFromConstructor(newTarget, "%Collator.prototype%", internalSlotsList). + auto* collator = TRY(ordinary_create_from_constructor(global_object, new_target, &GlobalObject::intl_collator_prototype)); + + // 6. Return ? InitializeCollator(collator, locales, options). + return collator; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.h b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.h new file mode 100644 index 0000000000..327ec3d098 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Intl { + +class CollatorConstructor final : public NativeFunction { + JS_OBJECT(CollatorConstructor, NativeFunction); + +public: + explicit CollatorConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~CollatorConstructor() override = default; + + virtual ThrowCompletionOr call() override; + virtual ThrowCompletionOr construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp new file mode 100644 index 0000000000..a5ef624274 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Intl { + +// 10.3 Properties of the Intl.Collator Prototype Object, https://tc39.es/ecma402/#sec-properties-of-the-intl-collator-prototype-object +CollatorPrototype::CollatorPrototype(GlobalObject& global_object) + : PrototypeObject(*global_object.object_prototype()) +{ +} + +void CollatorPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); + + auto& vm = this->vm(); + + // 10.3.2 Intl.Collator.prototype [ @@toStringTag ], https://tc39.es/ecma402/#sec-intl.collator.prototype-@@tostringtag + define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl.Collator"), Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h new file mode 100644 index 0000000000..24b3806b85 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Intl { + +class CollatorPrototype final : public PrototypeObject { + JS_PROTOTYPE_OBJECT(CollatorPrototype, Collator, Collator); + +public: + explicit CollatorPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~CollatorPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp index 06ea3a82d9..e681c794a0 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ void Intl::initialize(GlobalObject& global_object) define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl"), Attribute::Configurable); u8 attr = Attribute::Writable | Attribute::Configurable; + define_direct_property(vm.names.Collator, global_object.intl_collator_constructor(), attr); define_direct_property(vm.names.DateTimeFormat, global_object.intl_date_time_format_constructor(), attr); define_direct_property(vm.names.DisplayNames, global_object.intl_display_names_constructor(), attr); define_direct_property(vm.names.ListFormat, global_object.intl_list_format_constructor(), attr); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.@@toStringTag.js new file mode 100644 index 0000000000..a03d0e3f72 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.@@toStringTag.js @@ -0,0 +1,3 @@ +test("basic functionality", () => { + expect(Intl.Collator.prototype[Symbol.toStringTag]).toBe("Intl.Collator"); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.js new file mode 100644 index 0000000000..4b3abbf079 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.js @@ -0,0 +1,5 @@ +describe("normal behavior", () => { + test("length is 0", () => { + expect(Intl.Collator).toHaveLength(0); + }); +});