diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 921ce02bacd..a289b851955 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -123,6 +123,8 @@ set(SOURCES Runtime/SymbolPrototype.cpp Runtime/Temporal/AbstractOperations.cpp Runtime/Temporal/Calendar.cpp + Runtime/Temporal/CalendarConstructor.cpp + Runtime/Temporal/CalendarPrototype.cpp Runtime/Temporal/Instant.cpp Runtime/Temporal/InstantConstructor.cpp Runtime/Temporal/InstantPrototype.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 7df5cbec229..5032199e38d 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -76,8 +76,9 @@ __JS_ENUMERATE(Float32Array, float32_array, Float32ArrayPrototype, Float32ArrayConstructor, float) \ __JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double) -#define JS_ENUMERATE_TEMPORAL_OBJECTS \ - __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ +#define JS_ENUMERATE_TEMPORAL_OBJECTS \ + __JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor) \ + __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor) #define JS_ENUMERATE_ITERATOR_PROTOTYPES \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index ee685a50938..6220ae4f419 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -166,8 +166,9 @@ M(StringNonGlobalRegExp, "RegExp argument is non-global") \ M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \ M(StringRepeatCountMustBe, "repeat count must be a {} number") \ - M(TemporalInvalidISODate, "Invalid ISO date") \ + M(TemporalInvalidCalendarIdentifier, "Invalid calendar identifier '{}'") \ M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \ + M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidTime, "Invalid time") \ M(TemporalInvalidTimeZoneName, "Invalid time zone name") \ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 2b729d4deb5..a56cff9e547 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -68,6 +68,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index f9948eea02d..aa53db94bbb 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -4,11 +4,55 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include +#include #include namespace JS::Temporal { +// 12 Temporal.Calendar Objects, https://tc39.es/proposal-temporal/#sec-temporal-calendar-objects +Calendar::Calendar(String identifier, Object& prototype) + : Object(prototype) + , m_identifier(move(identifier)) +{ +} + +// 12.1.1 CreateTemporalCalendar ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalcalendar +Calendar* create_temporal_calendar(GlobalObject& global_object, String const& identifier, FunctionObject* new_target) +{ + auto& vm = global_object.vm(); + + // 1. Assert: ! IsBuiltinCalendar(identifier) is true. + VERIFY(is_builtin_calendar(identifier)); + + // 2. If newTarget is not provided, set newTarget to %Temporal.Calendar%. + if (!new_target) + new_target = global_object.temporal_calendar_constructor(); + + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Calendar.prototype%", « [[InitializedTemporalCalendar]], [[Identifier]] »). + // 4. Set object.[[Identifier]] to identifier. + auto* object = ordinary_create_from_constructor(global_object, *new_target, &GlobalObject::temporal_calendar_prototype, identifier); + if (vm.exception()) + return {}; + + // 5. Return object. + return object; +} + +// 12.1.2 IsBuiltinCalendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal-isbuiltincalendar +// NOTE: This is the minimum IsBuiltinCalendar implementation for engines without ECMA-402. +bool is_builtin_calendar(String const& identifier) +{ + // 1. If id is not "iso8601", return false. + if (identifier != "iso8601"sv) + return false; + + // 2. Return true. + return true; +} + // 12.1.30 IsISOLeapYear ( year ), https://tc39.es/proposal-temporal/#sec-temporal-isisoleapyear bool is_iso_leap_year(i32 year) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index edb742c5666..e7b8ba18390 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -1,15 +1,35 @@ /* * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2021, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include namespace JS::Temporal { +class Calendar final : public Object { + JS_OBJECT(Calendar, Object); + +public: + explicit Calendar(String identifier, Object& prototype); + virtual ~Calendar() override = default; + + String const& identifier() const { return m_identifier; } + +private: + // 12.5 Properties of Temporal.Calendar Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-calendar-instances + + // [[Identifier]] + String m_identifier; +}; + +Calendar* create_temporal_calendar(GlobalObject&, String const& identifier, FunctionObject* new_target = nullptr); +bool is_builtin_calendar(String const& identifier); bool is_iso_leap_year(i32 year); i32 iso_days_in_month(i32 year, i32 month); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarConstructor.cpp new file mode 100644 index 00000000000..c4b6b5863fa --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarConstructor.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS::Temporal { + +// 12.2 The Temporal.Calendar Constructor, https://tc39.es/proposal-temporal/#sec-temporal-calendar-constructor +CalendarConstructor::CalendarConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Calendar.as_string(), *global_object.function_prototype()) +{ +} + +void CalendarConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 12.3.1 Temporal.Calendar.prototype, https://tc39.es/proposal-temporal/#sec-temporal-calendar-prototype + define_direct_property(vm.names.prototype, global_object.temporal_calendar_prototype(), 0); + + define_direct_property(vm.names.length, Value(1), Attribute::Configurable); +} + +// 12.2.1 Temporal.Calendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal.calendar +Value CalendarConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined, then + // a. Throw a TypeError exception. + vm.throw_exception(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.Calendar"); + return {}; +} + +// 12.2.1 Temporal.Calendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal.calendar +Value CalendarConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 2. Set id to ? ToString(id). + auto identifier = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + // 3. If ! IsBuiltinCalendar(id) is false, then + if (!is_builtin_calendar(identifier)) { + // a. Throw a RangeError exception. + vm.throw_exception(global_object, ErrorType::TemporalInvalidCalendarIdentifier, identifier); + return {}; + } + + // 4. Return ? CreateTemporalCalendar(id, NewTarget). + return create_temporal_calendar(global_object, identifier, &new_target); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarConstructor.h new file mode 100644 index 00000000000..5ce91aaa72c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class CalendarConstructor final : public NativeFunction { + JS_OBJECT(CalendarConstructor, NativeFunction); + +public: + explicit CalendarConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~CalendarConstructor() 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/Temporal/CalendarPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp new file mode 100644 index 00000000000..d3626580853 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Temporal { + +// 12.4 Properties of the Temporal.Calendar Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-calendar-prototype-object +CalendarPrototype::CalendarPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void CalendarPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h new file mode 100644 index 00000000000..4f17d1b68f1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class CalendarPrototype final : public Object { + JS_OBJECT(CalendarPrototype, Object); + +public: + explicit CalendarPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~CalendarPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index b35583c7abc..83c208fb719 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -26,6 +27,7 @@ void Temporal::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_direct_property(vm.names.now, heap().allocate(global_object, global_object), attr); + define_direct_property(vm.names.Calendar, global_object.temporal_calendar_constructor(), attr); define_direct_property(vm.names.Instant, global_object.temporal_instant_constructor(), attr); define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr); } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.js new file mode 100644 index 00000000000..4ddefcf3d75 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.js @@ -0,0 +1,38 @@ +describe("errors", () => { + test("called without new", () => { + expect(() => { + Temporal.Calendar(); + }).toThrowWithMessage(TypeError, "Temporal.Calendar constructor must be called with 'new'"); + }); + + test("argument must be coercible to string", () => { + expect(() => { + new Temporal.Calendar({ + toString() { + throw new Error(); + }, + }); + }).toThrow(Error); + }); + + test("invalid calendar identifier", () => { + expect(() => { + new Temporal.Calendar("foo"); + }).toThrowWithMessage(RangeError, "Invalid calendar identifier 'foo'"); + }); +}); + +describe("normal behavior", () => { + test("length is 1", () => { + expect(Temporal.Calendar).toHaveLength(1); + }); + + test("basic functionality", () => { + const calendar = new Temporal.Calendar("iso8601"); + // FIXME: Enable this once Temporal.Calendar.prototype.id is implemented + // expect(calendar.id).toBe("iso8601"); + expect(typeof calendar).toBe("object"); + expect(calendar).toBeInstanceOf(Temporal.Calendar); + expect(Object.getPrototypeOf(calendar)).toBe(Temporal.Calendar.prototype); + }); +});