From 8e75e5fabba920de2e813677029a55eb79b1a06a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 5 Sep 2021 23:05:16 -0400 Subject: [PATCH] LibJS: Implement a nearly empty Intl.ListFormat object This adds plumbing for the Intl.ListFormat 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/Intl.cpp | 2 + .../LibJS/Runtime/Intl/ListFormat.cpp | 71 +++++++++++++++++++ .../Libraries/LibJS/Runtime/Intl/ListFormat.h | 53 ++++++++++++++ .../Runtime/Intl/ListFormatConstructor.cpp | 53 ++++++++++++++ .../Runtime/Intl/ListFormatConstructor.h | 28 ++++++++ .../Runtime/Intl/ListFormatPrototype.cpp | 28 ++++++++ .../LibJS/Runtime/Intl/ListFormatPrototype.h | 22 ++++++ .../ListFormat/ListFormat.@@toStringTag.js | 3 + .../builtins/Intl/ListFormat/ListFormat.js | 13 ++++ 12 files changed, 279 insertions(+) create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.@@toStringTag.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 7901a39830b..831ec8ee906 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -77,6 +77,9 @@ set(SOURCES Runtime/Intl/DisplayNamesConstructor.cpp Runtime/Intl/DisplayNamesPrototype.cpp Runtime/Intl/Intl.cpp + Runtime/Intl/ListFormat.cpp + Runtime/Intl/ListFormatConstructor.cpp + Runtime/Intl/ListFormatPrototype.cpp Runtime/Intl/Locale.cpp Runtime/Intl/LocaleConstructor.cpp Runtime/Intl/LocalePrototype.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index a25a4eb32e3..642cf470290 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -78,6 +78,7 @@ #define JS_ENUMERATE_INTL_OBJECTS \ __JS_ENUMERATE(DisplayNames, display_names, DisplayNamesPrototype, DisplayNamesConstructor) \ + __JS_ENUMERATE(ListFormat, list_format, ListFormatPrototype, ListFormatConstructor) \ __JS_ENUMERATE(Locale, locale, LocalePrototype, LocaleConstructor) #define JS_ENUMERATE_TEMPORAL_OBJECTS \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 0eaf20a4f75..19ea8700aa9 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp index e4d2873baff..325ab02c37a 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace JS::Intl { @@ -30,6 +31,7 @@ void Intl::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; 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); define_direct_property(vm.names.Locale, global_object.intl_locale_constructor(), attr); define_native_function(vm.names.getCanonicalLocales, get_canonical_locales, 1, attr); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp new file mode 100644 index 00000000000..b03d74fa329 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS::Intl { + +// 13 ListFomat Objects, https://tc39.es/ecma402/#listformat-objects +ListFormat::ListFormat(Object& prototype) + : Object(prototype) +{ +} + +void ListFormat::set_type(StringView type) +{ + if (type == "conjunction"sv) { + m_type = Type::Conjunction; + } else if (type == "disjunction"sv) { + m_type = Type::Disjunction; + } else if (type == "unit"sv) { + m_type = Type::Unit; + } else { + VERIFY_NOT_REACHED(); + } +} + +StringView ListFormat::type_string() const +{ + switch (m_type) { + case Type::Conjunction: + return "conjunction"sv; + case Type::Disjunction: + return "disjunction"sv; + case Type::Unit: + return "unit"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void ListFormat::set_style(StringView style) +{ + if (style == "narrow"sv) { + m_style = Style::Narrow; + } else if (style == "short"sv) { + m_style = Style::Short; + } else if (style == "long"sv) { + m_style = Style::Long; + } else { + VERIFY_NOT_REACHED(); + } +} + +StringView ListFormat::style_string() const +{ + switch (m_style) { + case Style::Narrow: + return "narrow"sv; + case Style::Short: + return "short"sv; + case Style::Long: + return "long"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h new file mode 100644 index 00000000000..4224ba49ec7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS::Intl { + +class ListFormat final : public Object { + JS_OBJECT(ListFormat, Object); + +public: + enum class Type { + Invalid, + Conjunction, + Disjunction, + Unit, + }; + + enum class Style { + Invalid, + Narrow, + Short, + Long, + }; + + ListFormat(Object& prototype); + virtual ~ListFormat() override = default; + + String const& locale() const { return m_locale; } + void set_locale(String locale) { m_locale = move(locale); } + + Type type() const { return m_type; } + void set_type(StringView type); + StringView type_string() const; + + Style style() const { return m_style; } + void set_style(StringView style); + StringView style_string() const; + +private: + String m_locale; // [[Locale]] + Type m_type { Type::Invalid }; // [[Type]] + Style m_style { Style::Invalid }; // [[Style]] +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp new file mode 100644 index 00000000000..bca493f359a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS::Intl { + +// 13.2 The Intl.ListFormat Constructor, https://tc39.es/ecma402/#sec-intl-listformat-constructor +ListFormatConstructor::ListFormatConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.ListFormat.as_string(), *global_object.function_prototype()) +{ +} + +void ListFormatConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 13.3.1 Intl.ListFormat.prototype, https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype + define_direct_property(vm.names.prototype, global_object.intl_list_format_prototype(), 0); + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); +} + +// 13.2.1 Intl.ListFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-Intl.ListFormat +Value ListFormatConstructor::call() +{ + // 1. If NewTarget is undefined, throw a TypeError exception. + vm().throw_exception(global_object(), ErrorType::ConstructorWithoutNew, "Intl.ListFormat"); + return {}; +} + +// 13.2.1 Intl.ListFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-Intl.ListFormat +Value ListFormatConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormat.prototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]], [[Templates]] »). + auto* list_format = ordinary_create_from_constructor(global_object, new_target, &GlobalObject::intl_list_format_prototype); + if (vm.exception()) + return {}; + + return list_format; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.h b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.h new file mode 100644 index 00000000000..30703ed0e36 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Intl { + +class ListFormatConstructor final : public NativeFunction { + JS_OBJECT(ListFormatConstructor, NativeFunction); + +public: + explicit ListFormatConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ListFormatConstructor() override = default; + + virtual Value call() override; + virtual Value construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp new file mode 100644 index 00000000000..6bde36f7149 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Intl { + +// 13.4 Properties of the Intl.ListFormat Prototype Object, https://tc39.es/ecma402/#sec-properties-of-intl-listformat-prototype-object +ListFormatPrototype::ListFormatPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void ListFormatPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); + + auto& vm = this->vm(); + + // 13.4.2 Intl.ListFormat.prototype [ @@toStringTag ], https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype-toStringTag + define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl.ListFormat"), Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.h new file mode 100644 index 00000000000..6bcbe6cf2f5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Intl { + +class ListFormatPrototype final : public Object { + JS_OBJECT(ListFormatPrototype, Object); + +public: + explicit ListFormatPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ListFormatPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.@@toStringTag.js new file mode 100644 index 00000000000..4a1c2583168 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.@@toStringTag.js @@ -0,0 +1,3 @@ +test("basic functionality", () => { + expect(Intl.ListFormat.prototype[Symbol.toStringTag]).toBe("Intl.ListFormat"); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.js new file mode 100644 index 00000000000..9f0ac109314 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/ListFormat/ListFormat.js @@ -0,0 +1,13 @@ +describe("errors", () => { + test("called without new", () => { + expect(() => { + Intl.ListFormat(); + }).toThrowWithMessage(TypeError, "Intl.ListFormat constructor must be called with 'new'"); + }); +}); + +describe("normal behavior", () => { + test("length is 0", () => { + expect(Intl.ListFormat).toHaveLength(0); + }); +});