Преглед на файлове

LibJS: Implement stringification Temporal.PlainMonthDay prototypes

Timothy Flynn преди 8 месеца
родител
ревизия
5bccb36a6f

+ 22 - 0
Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -84,6 +84,28 @@ ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM& vm, Object const& o
     return Overflow::Reject;
 }
 
+// 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption
+ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, Object const& options)
+{
+    // 1. Let stringValue be ? GetOption(options, "calendarName", STRING, « "auto", "always", "never", "critical" », "auto").
+    auto string_value = TRY(get_option(vm, options, vm.names.calendarName, OptionType::String, { "auto"sv, "always"sv, "never"sv, "critical"sv }, "auto"sv));
+
+    // 2. If stringValue is "always", return ALWAYS.
+    if (string_value.as_string().utf8_string_view() == "always"sv)
+        return ShowCalendar::Always;
+
+    // 3. If stringValue is "never", return NEVER.
+    if (string_value.as_string().utf8_string_view() == "never"sv)
+        return ShowCalendar::Never;
+
+    // 4. If stringValue is "critical", return CRITICAL.
+    if (string_value.as_string().utf8_string_view() == "critical"sv)
+        return ShowCalendar::Critical;
+
+    // 5. Return AUTO.
+    return ShowCalendar::Auto;
+}
+
 // 13.14 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
 ThrowCompletionOr<void> validate_temporal_rounding_increment(VM& vm, u64 increment, u64 dividend, bool inclusive)
 {

+ 8 - 0
Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

@@ -38,6 +38,13 @@ enum class Overflow {
     Reject,
 };
 
+enum class ShowCalendar {
+    Auto,
+    Always,
+    Never,
+    Critical,
+};
+
 enum class TimeStyle {
     Separated,
     Unseparated,
@@ -144,6 +151,7 @@ struct ParsedISODateTime {
 double iso_date_to_epoch_days(double year, double month, double date);
 double epoch_days_to_epoch_ms(double day, double time);
 ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& options);
+ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options);
 ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
 ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
 SecondsStringPrecision to_seconds_string_precision_record(UnitValue, Precision);

+ 18 - 0
Libraries/LibJS/Runtime/Temporal/Calendar.cpp

@@ -310,6 +310,24 @@ ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM& vm, StringView cal
     return result;
 }
 
+// 12.2.13 FormatCalendarAnnotation ( id, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation
+String format_calendar_annotation(StringView id, ShowCalendar show_calendar)
+{
+    // 1. If showCalendar is NEVER, return the empty String.
+    if (show_calendar == ShowCalendar::Never)
+        return String {};
+
+    // 2. If showCalendar is AUTO and id is "iso8601", return the empty String.
+    if (show_calendar == ShowCalendar::Auto && id == "iso8601"sv)
+        return String {};
+
+    // 3. If showCalendar is CRITICAL, let flag be "!"; else, let flag be the empty String.
+    auto flag = show_calendar == ShowCalendar::Critical ? "!"sv : ""sv;
+
+    // 4. Return the string-concatenation of "[", flag, "u-ca=", id, and "]".
+    return MUST(String::formatted("[{}u-ca={}]", flag, id));
+}
+
 // 12.2.15 ISODaysInMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
 u8 iso_days_in_month(double year, double month)
 {

+ 1 - 0
Libraries/LibJS/Runtime/Temporal/Calendar.h

@@ -100,6 +100,7 @@ ThrowCompletionOr<String> canonicalize_calendar(VM&, StringView id);
 Vector<String> const& available_calendars();
 ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
 ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
+String format_calendar_annotation(StringView id, ShowCalendar);
 u8 iso_days_in_month(double year, double month);
 YearWeek iso_week_of_year(ISODate const&);
 u16 iso_day_of_year(ISODate const&);

+ 17 - 0
Libraries/LibJS/Runtime/Temporal/PlainDate.cpp

@@ -86,6 +86,23 @@ bool is_valid_iso_date(double year, double month, double day)
     return true;
 }
 
+// 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
+String pad_iso_year(i32 year)
+{
+    // 1. If y ≥ 0 and y ≤ 9999, then
+    if (year >= 0 && year <= 9999) {
+        // a. Return ToZeroPaddedDecimalString(y, 4).
+        return MUST(String::formatted("{:04}", year));
+    }
+
+    // 2. If y > 0, let yearSign be "+"; otherwise, let yearSign be "-".
+    auto year_sign = year > 0 ? '+' : '-';
+
+    // 3. Let year be ToZeroPaddedDecimalString(abs(y), 6).
+    // 4. Return the string-concatenation of yearSign and year.
+    return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
+}
+
 // 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
 bool iso_date_within_limits(ISODate iso_date)
 {

+ 1 - 0
Libraries/LibJS/Runtime/Temporal/PlainDate.h

@@ -25,6 +25,7 @@ struct ISODate {
 ISODate create_iso_date_record(double year, double month, double day);
 ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow);
 bool is_valid_iso_date(double year, double month, double day);
+String pad_iso_year(i32 year);
 bool iso_date_within_limits(ISODate);
 
 }

+ 27 - 1
Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp

@@ -9,7 +9,6 @@
 #include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/Intrinsics.h>
 #include <LibJS/Runtime/Realm.h>
-#include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/Calendar.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
@@ -141,4 +140,31 @@ ThrowCompletionOr<GC::Ref<PlainMonthDay>> create_temporal_month_day(VM& vm, ISOD
     return object;
 }
 
+// 10.5.3 TemporalMonthDayToString ( monthDay, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporalmonthdaytostring
+String temporal_month_day_to_string(PlainMonthDay const& month_day, ShowCalendar show_calendar)
+{
+    // 1. Let month be ToZeroPaddedDecimalString(monthDay.[[ISODate]].[[Month]], 2).
+    // 2. Let day be ToZeroPaddedDecimalString(monthDay.[[ISODate]].[[Day]], 2).
+    // 3. Let result be the string-concatenation of month, the code unit 0x002D (HYPHEN-MINUS), and day.
+    auto result = MUST(String::formatted("{:02}-{:02}", month_day.iso_date().month, month_day.iso_date().day));
+
+    // 4. If showCalendar is one of ALWAYS or CRITICAL, or if monthDay.[[Calendar]] is not "iso8601", then
+    if (show_calendar == ShowCalendar::Always || show_calendar == ShowCalendar::Critical || month_day.calendar() != "iso8601"sv) {
+        // a. Let year be PadISOYear(monthDay.[[ISODate]].[[Year]]).
+        auto year = pad_iso_year(month_day.iso_date().year);
+
+        // b. Set result to the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and result.
+        result = MUST(String::formatted("{}-{}", year, result));
+    }
+
+    // 5. Let calendarString be FormatCalendarAnnotation(monthDay.[[Calendar]], showCalendar).
+    auto calendar_string = format_calendar_annotation(month_day.calendar(), show_calendar);
+
+    // 6. Set result to the string-concatenation of result and calendarString.
+    result = MUST(String::formatted("{}{}", result, calendar_string));
+
+    // 7. Return result.
+    return result;
+}
+
 }

+ 2 - 0
Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h

@@ -10,6 +10,7 @@
 #include <AK/String.h>
 #include <LibJS/Runtime/Completion.h>
 #include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 
 namespace JS::Temporal {
@@ -33,5 +34,6 @@ private:
 
 ThrowCompletionOr<GC::Ref<PlainMonthDay>> to_temporal_month_day(VM&, Value item, Value options = js_undefined());
 ThrowCompletionOr<GC::Ref<PlainMonthDay>> create_temporal_month_day(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
+String temporal_month_day_to_string(PlainMonthDay const&, ShowCalendar);
 
 }

+ 45 - 0
Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp

@@ -32,6 +32,11 @@ void PlainMonthDayPrototype::initialize(Realm& realm)
     define_native_accessor(realm, vm.names.calendarId, calendar_id_getter, {}, Attribute::Configurable);
     define_native_accessor(realm, vm.names.monthCode, month_code_getter, {}, Attribute::Configurable);
     define_native_accessor(realm, vm.names.day, day_getter, {}, Attribute::Configurable);
+
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+    define_native_function(realm, vm.names.toString, to_string, 0, attr);
+    define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
+    define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
 }
 
 // 10.3.3 get Temporal.PlainMonthDay.prototype.calendarId, https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.calendarid
@@ -67,4 +72,44 @@ JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::day_getter)
     return calendar_iso_to_date(month_day->calendar(), month_day->iso_date()).day;
 }
 
+// 10.3.8 Temporal.PlainMonthDay.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tostring
+JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_string)
+{
+    // 1. Let monthDay be the this value.
+    // 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
+    auto month_day = TRY(typed_this_object(vm));
+
+    // 3. Let resolvedOptions be ? GetOptionsObject(options).
+    auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
+
+    // 4. Let showCalendar be ? GetTemporalShowCalendarNameOption(resolvedOptions).
+    auto show_calendar = TRY(get_temporal_show_calendar_name_option(vm, resolved_options));
+
+    // 5. Return TemporalMonthDayToString(monthDay, showCalendar).
+    return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, show_calendar));
+}
+
+// 10.3.9 Temporal.PlainMonthDay.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
+// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
+JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_locale_string)
+{
+    // 1. Let monthDay be the this value.
+    // 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
+    auto month_day = TRY(typed_this_object(vm));
+
+    // 3. Return TemporalMonthDayToString(monthDay, auto).
+    return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, ShowCalendar::Auto));
+}
+
+// 10.3.10 Temporal.PlainMonthDay.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
+JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_json)
+{
+    // 1. Let monthDay be the this value.
+    // 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
+    auto month_day = TRY(typed_this_object(vm));
+
+    // 3. Return TemporalMonthDayToString(monthDay, auto).
+    return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, ShowCalendar::Auto));
+}
+
 }

+ 3 - 0
Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h

@@ -26,6 +26,9 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(calendar_id_getter);
     JS_DECLARE_NATIVE_FUNCTION(month_code_getter);
     JS_DECLARE_NATIVE_FUNCTION(day_getter);
+    JS_DECLARE_NATIVE_FUNCTION(to_string);
+    JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
+    JS_DECLARE_NATIVE_FUNCTION(to_json);
 };
 
 }

+ 6 - 6
Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.js

@@ -57,10 +57,10 @@ describe("normal behavior", () => {
         expect(Object.getPrototypeOf(plainMonthDay)).toBe(Temporal.PlainMonthDay.prototype);
     });
 
-    // FIXME: Re-implement this test with Temporal.PlainMonthDay.prototype.toString({ calendarName: "always" }).
-    // test("default reference year is 1972", () => {
-    //     const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
-    //     const fields = plainMonthDay.getISOFields();
-    //     expect(fields.isoYear).toBe(1972);
-    // });
+    test("default reference year is 1972", () => {
+        const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
+        const fields = plainMonthDay.toString({ calendarName: "always" });
+        const year = fields.split("-")[0];
+        expect(year).toBe("1972");
+    });
 });

+ 23 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.prototype.toJSON.js

@@ -0,0 +1,23 @@
+describe("correct behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.PlainMonthDay.prototype.toJSON).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        let plainMonthDay;
+
+        plainMonthDay = new Temporal.PlainMonthDay(7, 6);
+        expect(plainMonthDay.toJSON()).toBe("07-06");
+
+        plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
+        expect(plainMonthDay.toJSON()).toBe("2021-07-06[u-ca=gregory]");
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainMonthDay object", () => {
+        expect(() => {
+            Temporal.PlainMonthDay.prototype.toJSON.call("foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
+    });
+});

+ 23 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.prototype.toLocaleString.js

@@ -0,0 +1,23 @@
+describe("correct behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.PlainMonthDay.prototype.toLocaleString).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        let plainMonthDay;
+
+        plainMonthDay = new Temporal.PlainMonthDay(7, 6);
+        expect(plainMonthDay.toLocaleString()).toBe("07-06");
+
+        plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
+        expect(plainMonthDay.toLocaleString()).toBe("2021-07-06[u-ca=gregory]");
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainMonthDay object", () => {
+        expect(() => {
+            Temporal.PlainMonthDay.prototype.toLocaleString.call("foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
+    });
+});

+ 42 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.prototype.toString.js

@@ -0,0 +1,42 @@
+describe("correct behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.PlainMonthDay.prototype.toString).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        let plainMonthDay;
+
+        plainMonthDay = new Temporal.PlainMonthDay(7, 6);
+        expect(plainMonthDay.toString()).toBe("07-06");
+        expect(plainMonthDay.toString({ calendarName: "auto" })).toBe("07-06");
+        expect(plainMonthDay.toString({ calendarName: "always" })).toBe("1972-07-06[u-ca=iso8601]");
+        expect(plainMonthDay.toString({ calendarName: "never" })).toBe("07-06");
+        expect(plainMonthDay.toString({ calendarName: "critical" })).toBe(
+            "1972-07-06[!u-ca=iso8601]"
+        );
+
+        plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
+        expect(plainMonthDay.toString()).toBe("2021-07-06[u-ca=gregory]");
+        expect(plainMonthDay.toString({ calendarName: "auto" })).toBe("2021-07-06[u-ca=gregory]");
+        expect(plainMonthDay.toString({ calendarName: "always" })).toBe("2021-07-06[u-ca=gregory]");
+        expect(plainMonthDay.toString({ calendarName: "never" })).toBe("2021-07-06");
+        expect(plainMonthDay.toString({ calendarName: "critical" })).toBe(
+            "2021-07-06[!u-ca=gregory]"
+        );
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainMonthDay object", () => {
+        expect(() => {
+            Temporal.PlainMonthDay.prototype.toString.call("foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
+    });
+
+    test("calendarName option must be one of 'auto', 'always', 'never', 'critical'", () => {
+        const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
+        expect(() => {
+            plainMonthDay.toString({ calendarName: "foo" });
+        }).toThrowWithMessage(RangeError, "foo is not a valid value for option calendarName");
+    });
+});