Bug 1884206: Fix order of rounding* option reads and resolvedOptions. r=dminor
[gecko.git] / js / src / builtin / intl / PluralRules.js
blob260fdbd568e508a20fd0b76c4b685a5e378b7b03
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6  * PluralRules internal properties.
7  *
8  * 9.1 Internal slots of Service Constructors
9  * 16.2.3 Properties of the Intl.PluralRules Constructor, Internal slots
10  *
11  * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
12  */
13 var pluralRulesInternalProperties = {
14   localeData: pluralRulesLocaleData,
15   relevantExtensionKeys: [],
18 function pluralRulesLocaleData() {
19   // PluralRules don't support any extension keys.
20   return {};
23 /**
24  * 16.1.2 InitializePluralRules ( pluralRules, locales, options )
25  *
26  * Compute an internal properties object from |lazyPluralRulesData|.
27  *
28  * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
29  */
30 function resolvePluralRulesInternals(lazyPluralRulesData) {
31   assert(IsObject(lazyPluralRulesData), "lazy data not an object?");
33   var internalProps = std_Object_create(null);
35   var PluralRules = pluralRulesInternalProperties;
37   // Compute effective locale.
39   // Step 9.
40   var localeData = PluralRules.localeData;
42   // Step 10.
43   var r = ResolveLocale(
44     "PluralRules",
45     lazyPluralRulesData.requestedLocales,
46     lazyPluralRulesData.opt,
47     PluralRules.relevantExtensionKeys,
48     localeData
49   );
51   // Step 11.
52   internalProps.locale = r.locale;
54   // Step 7.
55   internalProps.type = lazyPluralRulesData.type;
57   // Step 8. SetNumberFormatDigitOptions, step 6.
58   internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
60   // Step 8. SetNumberFormatDigitOptions, step 14.
61   internalProps.roundingIncrement = lazyPluralRulesData.roundingIncrement;
63   // Step 8. SetNumberFormatDigitOptions, step 15.
64   internalProps.roundingMode = lazyPluralRulesData.roundingMode;
66   // Step 8. SetNumberFormatDigitOptions, step 16.
67   internalProps.trailingZeroDisplay = lazyPluralRulesData.trailingZeroDisplay;
69   // Step 8. SetNumberFormatDigitOptions, steps 25-26.
70   if ("minimumFractionDigits" in lazyPluralRulesData) {
71     assert(
72       "maximumFractionDigits" in lazyPluralRulesData,
73       "min/max frac digits mismatch"
74     );
75     internalProps.minimumFractionDigits =
76       lazyPluralRulesData.minimumFractionDigits;
77     internalProps.maximumFractionDigits =
78       lazyPluralRulesData.maximumFractionDigits;
79   }
81   // Step 8. SetNumberFormatDigitOptions, steps 24 and 26.
82   if ("minimumSignificantDigits" in lazyPluralRulesData) {
83     assert(
84       "maximumSignificantDigits" in lazyPluralRulesData,
85       "min/max sig digits mismatch"
86     );
87     internalProps.minimumSignificantDigits =
88       lazyPluralRulesData.minimumSignificantDigits;
89     internalProps.maximumSignificantDigits =
90       lazyPluralRulesData.maximumSignificantDigits;
91   }
93   // Step 8. SetNumberFormatDigitOptions, steps 26-30.
94   internalProps.roundingPriority = lazyPluralRulesData.roundingPriority;
96   // `pluralCategories` is lazily computed on first access.
97   internalProps.pluralCategories = null;
99   return internalProps;
103  * Returns an object containing the PluralRules internal properties of |obj|.
104  */
105 function getPluralRulesInternals(obj) {
106   assert(IsObject(obj), "getPluralRulesInternals called with non-object");
107   assert(
108     intl_GuardToPluralRules(obj) !== null,
109     "getPluralRulesInternals called with non-PluralRules"
110   );
112   var internals = getIntlObjectInternals(obj);
113   assert(
114     internals.type === "PluralRules",
115     "bad type escaped getIntlObjectInternals"
116   );
118   var internalProps = maybeInternalProperties(internals);
119   if (internalProps) {
120     return internalProps;
121   }
123   internalProps = resolvePluralRulesInternals(internals.lazyData);
124   setInternalProperties(internals, internalProps);
125   return internalProps;
129  * 16.1.2 InitializePluralRules ( pluralRules, locales, options )
131  * Initializes an object as a PluralRules.
133  * This method is complicated a moderate bit by its implementing initialization
134  * as a *lazy* concept.  Everything that must happen now, does -- but we defer
135  * all the work we can until the object is actually used as a PluralRules.
136  * This later work occurs in |resolvePluralRulesInternals|; steps not noted
137  * here occur there.
139  * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
140  */
141 function InitializePluralRules(pluralRules, locales, options) {
142   assert(IsObject(pluralRules), "InitializePluralRules called with non-object");
143   assert(
144     intl_GuardToPluralRules(pluralRules) !== null,
145     "InitializePluralRules called with non-PluralRules"
146   );
148   // Lazy PluralRules data has the following structure:
149   //
150   //   {
151   //     requestedLocales: List of locales,
152   //     type: "cardinal" / "ordinal",
153   //
154   //     opt: // opt object computer in InitializePluralRules
155   //       {
156   //         localeMatcher: "lookup" / "best fit",
157   //       }
158   //
159   //     minimumIntegerDigits: integer ∈ [1, 21],
160   //
161   //     // optional, mutually exclusive with the significant-digits option
162   //     minimumFractionDigits: integer ∈ [0, 100],
163   //     maximumFractionDigits: integer ∈ [0, 100],
164   //
165   //     // optional, mutually exclusive with the fraction-digits option
166   //     minimumSignificantDigits: integer ∈ [1, 21],
167   //     maximumSignificantDigits: integer ∈ [1, 21],
168   //
169   //     roundingPriority: "auto" / "lessPrecision" / "morePrecision",
170   //
171   //     trailingZeroDisplay: "auto" / "stripIfInteger",
172   //
173   //     roundingIncrement: integer ∈ (1, 2, 5,
174   //                                   10, 20, 25, 50,
175   //                                   100, 200, 250, 500,
176   //                                   1000, 2000, 2500, 5000),
177   //
178   //     roundingMode: "ceil" / "floor" / "expand" / "trunc" /
179   //                   "halfCeil" / "halfFloor" / "halfExpand" / "halfTrunc" / "halfEven",
180   //   }
181   //
182   // Note that lazy data is only installed as a final step of initialization,
183   // so every PluralRules lazy data object has *all* these properties, never a
184   // subset of them.
185   var lazyPluralRulesData = std_Object_create(null);
187   // Step 1.
188   var requestedLocales = CanonicalizeLocaleList(locales);
189   lazyPluralRulesData.requestedLocales = requestedLocales;
191   // Step 2. (Inlined call to CoerceOptionsToObject.)
192   if (options === undefined) {
193     options = std_Object_create(null);
194   } else {
195     options = ToObject(options);
196   }
198   // Step 3.
199   var opt = new_Record();
200   lazyPluralRulesData.opt = opt;
202   // Steps 4-5.
203   var matcher = GetOption(
204     options,
205     "localeMatcher",
206     "string",
207     ["lookup", "best fit"],
208     "best fit"
209   );
210   opt.localeMatcher = matcher;
212   // Steps 6-7.
213   var type = GetOption(
214     options,
215     "type",
216     "string",
217     ["cardinal", "ordinal"],
218     "cardinal"
219   );
220   lazyPluralRulesData.type = type;
222   // Step 8.
223   SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0, 3, "standard");
225   // Step 12.
226   //
227   // We've done everything that must be done now: mark the lazy data as fully
228   // computed and install it.
229   initializeIntlObject(pluralRules, "PluralRules", lazyPluralRulesData);
233  * 16.2.2 Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
235  * Returns the subset of the given locale list for which this locale list has a
236  * matching (possibly fallback) locale. Locales appear in the same order in the
237  * returned list as in the input list.
239  * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
240  */
241 function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
242   var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
244   // Step 1.
245   var availableLocales = "PluralRules";
247   // Step 2.
248   var requestedLocales = CanonicalizeLocaleList(locales);
250   // Step 3.
251   return SupportedLocales(availableLocales, requestedLocales, options);
255  * 16.3.3 Intl.PluralRules.prototype.select ( value )
257  * Returns a String value representing the plural category matching
258  * the number passed as value according to the
259  * effective locale and the formatting options of this PluralRules.
261  * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
262  */
263 function Intl_PluralRules_select(value) {
264   // Step 1.
265   var pluralRules = this;
267   // Step 2.
268   if (
269     !IsObject(pluralRules) ||
270     (pluralRules = intl_GuardToPluralRules(pluralRules)) === null
271   ) {
272     return callFunction(
273       intl_CallPluralRulesMethodIfWrapped,
274       this,
275       value,
276       "Intl_PluralRules_select"
277     );
278   }
280   // Step 3.
281   var n = ToNumber(value);
283   // Ensure the PluralRules internals are resolved.
284   getPluralRulesInternals(pluralRules);
286   // Step 4.
287   return intl_SelectPluralRule(pluralRules, n);
291  * 16.3.4 Intl.PluralRules.prototype.selectRange ( start, end )
293  * Returns a String value representing the plural category matching the input
294  * number range according to the effective locale and the formatting options
295  * of this PluralRules.
297  * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
298  */
299 function Intl_PluralRules_selectRange(start, end) {
300   // Step 1.
301   var pluralRules = this;
303   // Step 2.
304   if (
305     !IsObject(pluralRules) ||
306     (pluralRules = intl_GuardToPluralRules(pluralRules)) === null
307   ) {
308     return callFunction(
309       intl_CallPluralRulesMethodIfWrapped,
310       this,
311       start,
312       end,
313       "Intl_PluralRules_selectRange"
314     );
315   }
317   // Step 3.
318   if (start === undefined || end === undefined) {
319     ThrowTypeError(
320       JSMSG_UNDEFINED_NUMBER,
321       start === undefined ? "start" : "end",
322       "PluralRules",
323       "selectRange"
324     );
325   }
327   // Step 4.
328   var x = ToNumber(start);
330   // Step 5.
331   var y = ToNumber(end);
333   // Step 6.
334   return intl_SelectPluralRuleRange(pluralRules, x, y);
338  * 16.3.5 Intl.PluralRules.prototype.resolvedOptions ( )
340  * Returns the resolved options for a PluralRules object.
342  * ES2024 Intl draft rev a1db4567870dbe505121a4255f1210338757190a
343  */
344 function Intl_PluralRules_resolvedOptions() {
345   // Step 1.
346   var pluralRules = this;
348   // Step 2.
349   if (
350     !IsObject(pluralRules) ||
351     (pluralRules = intl_GuardToPluralRules(pluralRules)) === null
352   ) {
353     return callFunction(
354       intl_CallPluralRulesMethodIfWrapped,
355       this,
356       "Intl_PluralRules_resolvedOptions"
357     );
358   }
360   var internals = getPluralRulesInternals(pluralRules);
362   // Step 4.
363   var internalsPluralCategories = internals.pluralCategories;
364   if (internalsPluralCategories === null) {
365     internalsPluralCategories = intl_GetPluralCategories(pluralRules);
366     internals.pluralCategories = internalsPluralCategories;
367   }
369   // Step 5.b.
370   var pluralCategories = [];
371   for (var i = 0; i < internalsPluralCategories.length; i++) {
372     DefineDataProperty(pluralCategories, i, internalsPluralCategories[i]);
373   }
375   // Steps 3 and 5.
376   var result = {
377     locale: internals.locale,
378     type: internals.type,
379     minimumIntegerDigits: internals.minimumIntegerDigits,
380   };
382   // Min/Max fraction digits are either both present or not present at all.
383   assert(
384     hasOwn("minimumFractionDigits", internals) ===
385       hasOwn("maximumFractionDigits", internals),
386     "minimumFractionDigits is present iff maximumFractionDigits is present"
387   );
389   if (hasOwn("minimumFractionDigits", internals)) {
390     DefineDataProperty(
391       result,
392       "minimumFractionDigits",
393       internals.minimumFractionDigits
394     );
395     DefineDataProperty(
396       result,
397       "maximumFractionDigits",
398       internals.maximumFractionDigits
399     );
400   }
402   // Min/Max significant digits are either both present or not present at all.
403   assert(
404     hasOwn("minimumSignificantDigits", internals) ===
405       hasOwn("maximumSignificantDigits", internals),
406     "minimumSignificantDigits is present iff maximumSignificantDigits is present"
407   );
409   if (hasOwn("minimumSignificantDigits", internals)) {
410     DefineDataProperty(
411       result,
412       "minimumSignificantDigits",
413       internals.minimumSignificantDigits
414     );
415     DefineDataProperty(
416       result,
417       "maximumSignificantDigits",
418       internals.maximumSignificantDigits
419     );
420   }
422   DefineDataProperty(result, "pluralCategories", pluralCategories);
423   DefineDataProperty(result, "roundingIncrement", internals.roundingIncrement);
424   DefineDataProperty(result, "roundingMode", internals.roundingMode);
425   DefineDataProperty(result, "roundingPriority", internals.roundingPriority);
426   DefineDataProperty(
427     result,
428     "trailingZeroDisplay",
429     internals.trailingZeroDisplay
430   );
432   // Step 6.
433   return result;