1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 23:27:43 +00:00

LibJS: Validate Calendar.prototype.fields() values more strictly

This is a normative change in the Temporal spec.

See:

- 75b66d8
- 9c2262b
This commit is contained in:
Linus Groh 2021-09-02 21:31:41 +01:00
parent fb7b4caa57
commit 97cc8f4613
3 changed files with 88 additions and 5 deletions

View file

@ -173,7 +173,10 @@
M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \
M(StringRepeatCountMustBe, "repeat count must be a {} number") \
M(TemporalAmbiguousMonthOfPlainMonthDay, "Accessing month of PlainMonthDay is ambiguous, use monthCode instead") \
M(TemporalDuplicateCalendarField, "Duplicate calendar field '{}'") \
M(TemporalInvalidCalendar, "Invalid calendar") \
M(TemporalInvalidCalendarFieldName, "Invalid calendar field '{}'") \
M(TemporalInvalidCalendarFieldValue, "Invalid calendar field {}, expected a string") \
M(TemporalInvalidCalendarFunctionResult, "Invalid calendar, {}() function returned {}") \
M(TemporalInvalidCalendarIdentifier, "Invalid calendar identifier '{}'") \
M(TemporalInvalidDuration, "Invalid duration") \

View file

@ -7,6 +7,7 @@
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/CalendarPrototype.h>
@ -546,12 +547,66 @@ JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::fields)
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let fieldNames be ? IterableToListOfType(fields, « String »).
auto field_names = iterable_to_list_of_type(global_object, fields, { OptionType::String });
// 4. Let iteratorRecord be ? Getiterator(fields, sync).
auto* iterator_record = get_iterator(global_object, fields, IteratorHint::Sync);
if (vm.exception())
return {};
// 5. Return ! CreateArrayFromList(fieldNames).
// 5. Let fieldNames be a new empty List.
auto field_names = MarkedValueList { vm.heap() };
// 6. Let next be true.
// 7. Repeat, while next is not false,
while (true) {
// a. Set next to ? IteratorStep(iteratorRecord).
auto* next = iterator_step(global_object, *iterator_record);
if (vm.exception())
return {};
// b. If next is not false, then
if (!next)
break;
// i. Let nextValue be ? IteratorValue(next).
auto next_value = iterator_value(global_object, *next);
if (vm.exception())
return {};
// ii. If Type(nextValue) is not String, then
if (!next_value.is_string()) {
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalInvalidCalendarFieldValue, next_value.to_string_without_side_effects());
// 2. Return ? IteratorClose(iteratorRecord, completion).
iterator_close(*iterator_record);
return {};
}
// iii. If fieldNames contains nextValue, then
if (field_names.contains_slow(next_value)) {
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalDuplicateCalendarField, next_value.as_string().string());
// 2. Return ? IteratorClose(iteratorRecord, completion).
iterator_close(*iterator_record);
return {};
}
// iv. If nextValue is not one of "year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", then
if (!next_value.as_string().string().is_one_of("year"sv, "month"sv, "monthCode"sv, "day"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidCalendarFieldName, next_value.as_string().string());
// 2. Return ? IteratorClose(iteratorRecord, completion).
iterator_close(*iterator_record);
return {};
}
// v. Append nextValue to the end of the List fieldNames.
field_names.append(next_value);
}
// 8. Return ! CreateArrayFromList(fieldNames).
return Array::create_from(global_object, field_names);
}

View file

@ -5,7 +5,18 @@ describe("correct behavior", () => {
test("basic functionality", () => {
const calendar = new Temporal.Calendar("iso8601");
const array = ["foo", "bar", "baz"];
const array = [
"year",
"month",
"monthCode",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
];
const fields = calendar.fields(array);
expect(fields).toEqual(array);
expect(fields).not.toBe(array);
@ -24,7 +35,21 @@ describe("errors", () => {
for (const value of [123, null, undefined, true, {}]) {
expect(() => {
calendar.fields([value]);
}).toThrowWithMessage(TypeError, "FIXME: Add a string for this error.");
}).toThrowWithMessage(TypeError, `Invalid calendar field ${value}, expected a string`);
}
});
test("iterator values must be valid field names", () => {
const calendar = new Temporal.Calendar("iso8601");
expect(() => {
calendar.fields(["foo"]);
}).toThrowWithMessage(RangeError, "Invalid calendar field 'foo'");
});
test("iterator values must not contain duplicates", () => {
const calendar = new Temporal.Calendar("iso8601");
expect(() => {
calendar.fields(["year", "month", "year", "month"]);
}).toThrowWithMessage(RangeError, "Duplicate calendar field 'year'");
});
});