Skip to content

Commit 9ccd1d5

Browse files
fix(mf2): Be stricter about :integer options
1 parent eb9a0b2 commit 9ccd1d5

File tree

2 files changed

+56
-42
lines changed

2 files changed

+56
-42
lines changed

mf2/messageformat/src/functions/currency.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ export function currency(
2121
): MessageNumber {
2222
const { source } = ctx;
2323
const input = readNumericOperand(operand, source);
24-
const value = input.value;
25-
const options: MessageNumberOptions = Object.assign({}, input.options);
24+
const options: MessageNumberOptions = Object.assign({}, input.options, {
25+
localeMatcher: ctx.localeMatcher,
26+
style: 'currency'
27+
} as const);
2628

27-
options.style = 'currency';
2829
for (const [name, optval] of Object.entries(exprOpt)) {
2930
if (optval === undefined) continue;
3031
try {
@@ -34,6 +35,7 @@ export function currency(
3435
case 'roundingMode':
3536
case 'roundingPriority':
3637
case 'trailingZeroDisplay':
38+
case 'useGrouping':
3739
// @ts-expect-error Let Intl.NumberFormat construction fail
3840
options[name] = asString(optval);
3941
break;
@@ -85,12 +87,6 @@ export function currency(
8587
options[name] = strval;
8688
break;
8789
}
88-
case 'useGrouping': {
89-
const strval = asString(optval);
90-
// @ts-expect-error TS type is wrong
91-
options[name] = strval === 'never' ? false : strval;
92-
break;
93-
}
9490
}
9591
} catch (error) {
9692
if (error instanceof MessageError) {
@@ -107,5 +103,5 @@ export function currency(
107103
throw new MessageResolutionError('bad-operand', msg, source);
108104
}
109105

110-
return getMessageNumber(ctx, value, options);
106+
return getMessageNumber(ctx, input.value, options);
111107
}

mf2/messageformat/src/functions/number.ts

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ export interface MessageNumberPart extends MessageExpressionPart {
4040
export type MessageNumberOptions = Intl.NumberFormatOptions &
4141
Intl.PluralRulesOptions & { select?: 'exact' | 'cardinal' | 'ordinal' };
4242

43-
const INT = Symbol('INT');
44-
4543
export function readNumericOperand(
4644
value: unknown,
4745
source: string
@@ -73,6 +71,9 @@ export function getMessageNumber(
7371
value: number | bigint,
7472
options: MessageNumberOptions
7573
): MessageNumber {
74+
// @ts-expect-error We may have been a bit naughty earlier.
75+
if (options.useGrouping === 'never') options.useGrouping = false;
76+
7677
let locale: string | undefined;
7778
let nf: Intl.NumberFormat | undefined;
7879
let cat: Intl.LDMLPluralRule | undefined;
@@ -121,7 +122,7 @@ export function getMessageNumber(
121122

122123
/**
123124
* `number` accepts a number, BigInt or string representing a JSON number as input
124-
* and formats it with the same options as
125+
* and formats it with a subset of the options of
125126
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat | Intl.NumberFormat}.
126127
* It also supports plural category selection via
127128
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules | Intl.PluralRules}.
@@ -130,15 +131,15 @@ export function getMessageNumber(
130131
*/
131132
export function number(
132133
ctx: MessageFunctionContext,
133-
exprOpt: Record<string | symbol, unknown>,
134+
exprOpt: Record<string, unknown>,
134135
operand?: unknown
135136
): MessageNumber {
136137
const input = readNumericOperand(operand, ctx.source);
137138
const value = input.value;
138-
const options: MessageNumberOptions = Object.assign(
139-
{ localeMatcher: ctx.localeMatcher },
140-
input.options
141-
);
139+
const options: MessageNumberOptions = Object.assign({}, input.options, {
140+
localeMatcher: ctx.localeMatcher,
141+
style: 'decimal'
142+
} as const);
142143

143144
for (const [name, optval] of Object.entries(exprOpt)) {
144145
if (optval === undefined) continue;
@@ -153,17 +154,12 @@ export function number(
153154
// @ts-expect-error TS types don't know about roundingIncrement
154155
options[name] = asPositiveInteger(optval);
155156
break;
156-
case 'useGrouping': {
157-
const strval = asString(optval);
158-
// @ts-expect-error TS type is wrong
159-
options[name] = strval === 'never' ? false : strval;
160-
break;
161-
}
162157
case 'roundingMode':
163158
case 'roundingPriority':
164159
case 'select': // Called 'type' in Intl.PluralRules
165160
case 'signDisplay':
166161
case 'trailingZeroDisplay':
162+
case 'useGrouping':
167163
// @ts-expect-error Let Intl.NumberFormat construction fail
168164
options[name] = asString(optval);
169165
}
@@ -173,32 +169,54 @@ export function number(
173169
}
174170
}
175171

176-
const num =
177-
Number.isFinite(value) && exprOpt[INT]
178-
? Math.round(value as number)
179-
: value;
180-
181-
return getMessageNumber(ctx, num, options);
172+
return getMessageNumber(ctx, value, options);
182173
}
183174

184175
/**
185176
* `integer` accepts a number, BigInt or string representing a JSON number as input
186-
* and formats it with the same options as
177+
* and formats it with a subset of the options of
187178
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat | Intl.NumberFormat}.
188179
* It also supports plural category selection via
189180
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules | Intl.PluralRules}.
190181
*
191-
* The `maximumFractionDigits=0` and `style='decimal'` options are fixed for `:integer`.
192-
*
193182
* @beta
194183
*/
195-
export const integer = (
184+
export function integer(
196185
ctx: MessageFunctionContext,
197-
options: Record<string, unknown>,
198-
input?: unknown
199-
) =>
200-
number(
201-
ctx,
202-
{ ...options, maximumFractionDigits: 0, style: 'decimal', [INT]: true },
203-
input
204-
);
186+
exprOpt: Record<string, unknown>,
187+
operand?: unknown
188+
) {
189+
const input = readNumericOperand(operand, ctx.source);
190+
const value = Number.isFinite(input.value)
191+
? Math.round(input.value as number)
192+
: input.value;
193+
const options: MessageNumberOptions = Object.assign({}, input.options, {
194+
//localeMatcher: ctx.localeMatcher,
195+
maximumFractionDigits: 0,
196+
minimumFractionDigits: undefined,
197+
minimumSignificantDigits: undefined,
198+
style: 'decimal'
199+
} as const);
200+
201+
for (const [name, optval] of Object.entries(exprOpt)) {
202+
if (optval === undefined) continue;
203+
try {
204+
switch (name) {
205+
case 'minimumIntegerDigits':
206+
case 'maximumSignificantDigits':
207+
options[name] = asPositiveInteger(optval);
208+
break;
209+
case 'select': // Called 'type' in Intl.PluralRules
210+
case 'signDisplay':
211+
case 'useGrouping':
212+
// @ts-expect-error Let Intl.NumberFormat construction fail
213+
options[name] = asString(optval);
214+
}
215+
} catch {
216+
const msg = `Value ${optval} is not valid for :integer option ${name}`;
217+
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
218+
}
219+
}
220+
221+
return getMessageNumber(ctx, value, options);
222+
}

0 commit comments

Comments
 (0)