Procházet zdrojové kódy

LibJS: Allow out-of-order number ranges to be formatted

This is a normative change to the Intl NumberFormat V3 spec:
https://github.com/tc39/proposal-intl-numberformat-v3/commit/0c3d849
Timothy Flynn před 3 roky
rodič
revize
fd7d97fba5

+ 0 - 1
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -53,7 +53,6 @@
     M(IntlNumberIsNaN, "{} must not be NaN")                                                                                            \
     M(IntlNumberIsNaNOrInfinity, "Number must not be NaN or Infinity")                                                                  \
     M(IntlNumberIsNaNOrOutOfRange, "Value {} is NaN or is not between {} and {}")                                                       \
-    M(IntlNumberRangeIsInvalid, "Numeric range is invalid: {}")                                                                         \
     M(IntlOptionUndefined, "Option {} must be defined when option {} is {}")                                                            \
     M(IntlNonNumericOr2DigitAfterNumericOr2Digit, "Styles other than 'numeric' and '2-digit' may not be used in smaller units after "   \
                                                   "being used in larger units")                                                         \

+ 11 - 50
Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp

@@ -1729,90 +1729,51 @@ ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_number_range_pat
     if (end.is_nan())
         return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberIsNaN, "end"sv);
 
-    // 2. If x is a mathematical value, then
-    if (start.is_mathematical_value()) {
-        // a. If y is a mathematical value and y < x, throw a RangeError exception.
-        if (end.is_mathematical_value() && end.is_less_than(start))
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is a mathematical value, end is a mathematical value and end < start"sv);
-
-        // b. Else if y is -∞, throw a RangeError exception.
-        if (end.is_negative_infinity())
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is a mathematical value, end is -∞"sv);
-
-        // c. Else if y is -0𝔽 and x ≥ 0, throw a RangeError exception.
-        if (end.is_negative_zero() && (start.is_zero() || start.is_positive()))
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is a mathematical value, end is -0 and start ≥ 0"sv);
-    }
-    // 3. Else if x is +∞, then
-    else if (start.is_positive_infinity()) {
-        // a. If y is a mathematical value, throw a RangeError exception.
-        if (end.is_mathematical_value())
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is +∞, end is a mathematical value"sv);
-
-        // b. Else if y is -∞, throw a RangeError exception.
-        if (end.is_negative_infinity())
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is +∞, end is -∞"sv);
-
-        // c. Else if y is -0𝔽, throw a RangeError exception.
-        if (end.is_negative_zero())
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is +∞, end is -0"sv);
-    }
-    // 4. Else if x is -0𝔽, then
-    else if (start.is_negative_zero()) {
-        // a. If y is a mathematical value and y < 0, throw a RangeError exception.
-        if (end.is_mathematical_value() && end.is_negative())
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is -0, end is a mathematical value and end < 0"sv);
-
-        // b. Else if y is -∞, throw a RangeError exception.
-        if (end.is_negative_infinity())
-            return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberRangeIsInvalid, "start is -0, end is -∞"sv);
-    }
-
-    // 5. Let result be a new empty List.
+    // 2. Let result be a new empty List.
     Vector<PatternPartitionWithSource> result;
 
-    // 6. Let xResult be ? PartitionNumberPattern(numberFormat, x).
+    // 3. Let xResult be ? PartitionNumberPattern(numberFormat, x).
     auto raw_start_result = partition_number_pattern(global_object, number_format, move(start));
     auto start_result = PatternPartitionWithSource::create_from_parent_list(move(raw_start_result));
 
-    // 7. Let yResult be ? PartitionNumberPattern(numberFormat, y).
+    // 4. Let yResult be ? PartitionNumberPattern(numberFormat, y).
     auto raw_end_result = partition_number_pattern(global_object, number_format, move(end));
     auto end_result = PatternPartitionWithSource::create_from_parent_list(move(raw_end_result));
 
-    // 8. If xResult is equal to yResult, return FormatApproximately(numberFormat, xResult).
+    // 5. If xResult is equal to yResult, return FormatApproximately(numberFormat, xResult).
     if (start_result == end_result)
         return format_approximately(number_format, move(start_result));
 
-    // 9. For each r in xResult, do
+    // 6. For each r in xResult, do
     for (auto& part : start_result) {
         // i. Set r.[[Source]] to "startRange".
         part.source = "startRange"sv;
     }
 
-    // 10. Add all elements in xResult to result in order.
+    // 7. Add all elements in xResult to result in order.
     result = move(start_result);
 
-    // 11. Let rangeSeparator be an ILND String value used to separate two numbers.
+    // 8. Let rangeSeparator be an ILND String value used to separate two numbers.
     auto range_separator_symbol = Unicode::get_number_system_symbol(number_format.data_locale(), number_format.numbering_system(), Unicode::NumericSymbol::RangeSeparator).value_or("-"sv);
     auto range_separator = Unicode::augment_range_pattern(range_separator_symbol, result.last().value, end_result[0].value);
 
-    // 12. Append a new Record { [[Type]]: "literal", [[Value]]: rangeSeparator, [[Source]]: "shared" } element to result.
+    // 9. Append a new Record { [[Type]]: "literal", [[Value]]: rangeSeparator, [[Source]]: "shared" } element to result.
     PatternPartitionWithSource part;
     part.type = "literal"sv;
     part.value = range_separator.value_or(range_separator_symbol);
     part.source = "shared"sv;
     result.append(move(part));
 
-    // 13. For each r in yResult, do
+    // 10. For each r in yResult, do
     for (auto& part : end_result) {
         // a. Set r.[[Source]] to "endRange".
         part.source = "endRange"sv;
     }
 
-    // 14. Add all elements in yResult to result in order.
+    // 11. Add all elements in yResult to result in order.
     result.extend(move(end_result));
 
-    // 15. Return ! CollapseNumberRange(result).
+    // 12. Return ! CollapseNumberRange(result).
     return collapse_number_range(move(result));
 }
 

+ 22 - 35
Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatRange.js

@@ -33,41 +33,6 @@ describe("errors", () => {
         expect(() => {
             new Intl.NumberFormat().formatRange(1, NaN);
         }).toThrowWithMessage(RangeError, "end must not be NaN");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(1, 0);
-        }).toThrowWithMessage(
-            RangeError,
-            "start is a mathematical value, end is a mathematical value and end < start"
-        );
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(1, -Infinity);
-        }).toThrowWithMessage(RangeError, "start is a mathematical value, end is -∞");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(1, -0);
-        }).toThrowWithMessage(RangeError, "start is a mathematical value, end is -0 and start ≥ 0");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(Infinity, 0);
-        }).toThrowWithMessage(RangeError, "start is +∞, end is a mathematical value");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(Infinity, -Infinity);
-        }).toThrowWithMessage(RangeError, "start is +∞, end is -∞");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(Infinity, -0);
-        }).toThrowWithMessage(RangeError, "start is +∞, end is -0");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(-0, -1);
-        }).toThrowWithMessage(RangeError, "start is -0, end is a mathematical value and end < 0");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRange(-0, -Infinity);
-        }).toThrowWithMessage(RangeError, "start is -0, end is -∞");
     });
 });
 
@@ -137,4 +102,26 @@ describe("correct behavior", () => {
         });
         expect(ja2.formatRange(3, 5)).toBe("¥3 ~ ¥5");
     });
+
+    test("numbers in reverse order", () => {
+        const en = new Intl.NumberFormat("en");
+        expect(en.formatRange(1, 0)).toBe("1–0");
+        expect(en.formatRange(1, -Infinity)).toBe("1 – -∞");
+        expect(en.formatRange(1, -0)).toBe("1 – -0");
+        expect(en.formatRange(Infinity, 0)).toBe("∞ – 0");
+        expect(en.formatRange(Infinity, -Infinity)).toBe("∞ – -∞");
+        expect(en.formatRange(Infinity, -0)).toBe("∞ – -0");
+        expect(en.formatRange(-0, -1)).toBe("-0 – -1");
+        expect(en.formatRange(-0, -Infinity)).toBe("-0 – -∞");
+
+        const ja = new Intl.NumberFormat("ja");
+        expect(ja.formatRange(1, 0)).toBe("1~0");
+        expect(ja.formatRange(1, -Infinity)).toBe("1 ~ -∞");
+        expect(ja.formatRange(1, -0)).toBe("1 ~ -0");
+        expect(ja.formatRange(Infinity, 0)).toBe("∞ ~ 0");
+        expect(ja.formatRange(Infinity, -Infinity)).toBe("∞ ~ -∞");
+        expect(ja.formatRange(Infinity, -0)).toBe("∞ ~ -0");
+        expect(ja.formatRange(-0, -1)).toBe("-0 ~ -1");
+        expect(ja.formatRange(-0, -Infinity)).toBe("-0 ~ -∞");
+    });
 });

+ 44 - 35
Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatRangeToParts.js

@@ -33,41 +33,6 @@ describe("errors", () => {
         expect(() => {
             new Intl.NumberFormat().formatRangeToParts(1, NaN);
         }).toThrowWithMessage(RangeError, "end must not be NaN");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(1, 0);
-        }).toThrowWithMessage(
-            RangeError,
-            "start is a mathematical value, end is a mathematical value and end < start"
-        );
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(1, -Infinity);
-        }).toThrowWithMessage(RangeError, "start is a mathematical value, end is -∞");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(1, -0);
-        }).toThrowWithMessage(RangeError, "start is a mathematical value, end is -0 and start ≥ 0");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(Infinity, 0);
-        }).toThrowWithMessage(RangeError, "start is +∞, end is a mathematical value");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(Infinity, -Infinity);
-        }).toThrowWithMessage(RangeError, "start is +∞, end is -∞");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(Infinity, -0);
-        }).toThrowWithMessage(RangeError, "start is +∞, end is -0");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(-0, -1);
-        }).toThrowWithMessage(RangeError, "start is -0, end is a mathematical value and end < 0");
-
-        expect(() => {
-            new Intl.NumberFormat().formatRangeToParts(-0, -Infinity);
-        }).toThrowWithMessage(RangeError, "start is -0, end is -∞");
     });
 });
 
@@ -165,4 +130,48 @@ describe("correct behavior", () => {
             { type: "integer", value: "5", source: "endRange" },
         ]);
     });
+
+    test("numbers in reverse order", () => {
+        const en = new Intl.NumberFormat("en");
+        expect(en.formatRangeToParts(1, -Infinity)).toEqual([
+            { type: "integer", value: "1", source: "startRange" },
+            { type: "literal", value: " – ", source: "shared" },
+            { type: "minusSign", value: "-", source: "endRange" },
+            { type: "infinity", value: "∞", source: "endRange" },
+        ]);
+        expect(en.formatRangeToParts(Infinity, -Infinity)).toEqual([
+            { type: "infinity", value: "∞", source: "startRange" },
+            { type: "literal", value: " – ", source: "shared" },
+            { type: "minusSign", value: "-", source: "endRange" },
+            { type: "infinity", value: "∞", source: "endRange" },
+        ]);
+        expect(en.formatRangeToParts(-0, -Infinity)).toEqual([
+            { type: "minusSign", value: "-", source: "startRange" },
+            { type: "integer", value: "0", source: "startRange" },
+            { type: "literal", value: " – ", source: "shared" },
+            { type: "minusSign", value: "-", source: "endRange" },
+            { type: "infinity", value: "∞", source: "endRange" },
+        ]);
+
+        const ja = new Intl.NumberFormat("ja");
+        expect(ja.formatRangeToParts(1, -Infinity)).toEqual([
+            { type: "integer", value: "1", source: "startRange" },
+            { type: "literal", value: " ~ ", source: "shared" },
+            { type: "minusSign", value: "-", source: "endRange" },
+            { type: "infinity", value: "∞", source: "endRange" },
+        ]);
+        expect(ja.formatRangeToParts(Infinity, -Infinity)).toEqual([
+            { type: "infinity", value: "∞", source: "startRange" },
+            { type: "literal", value: " ~ ", source: "shared" },
+            { type: "minusSign", value: "-", source: "endRange" },
+            { type: "infinity", value: "∞", source: "endRange" },
+        ]);
+        expect(ja.formatRangeToParts(-0, -Infinity)).toEqual([
+            { type: "minusSign", value: "-", source: "startRange" },
+            { type: "integer", value: "0", source: "startRange" },
+            { type: "literal", value: " ~ ", source: "shared" },
+            { type: "minusSign", value: "-", source: "endRange" },
+            { type: "infinity", value: "∞", source: "endRange" },
+        ]);
+    });
 });