Skip to content

Commit c36db1f

Browse files
feat(mf2): Add MessageFunctionError, drop MessageSelectionError
1 parent a496195 commit c36db1f

File tree

15 files changed

+178
-141
lines changed

15 files changed

+178
-141
lines changed

mf2/icu-messageformat-1/src/functions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MessageResolutionError } from 'messageformat';
1+
import { MessageFunctionError } from 'messageformat';
22
import {
33
DefaultFunctions,
44
DraftFunctions,
@@ -15,7 +15,7 @@ function checkArgStyle(
1515
const argStyle = options['mf1:argStyle'];
1616
if (argStyle) {
1717
const msg = `Unsupported MF1 number argStyle: ${argStyle}`;
18-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
18+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
1919
}
2020
}
2121

@@ -108,7 +108,7 @@ function plural(
108108
else if (typeof num === 'bigint') num -= BigInt(offset);
109109
} else {
110110
const msg = `Plural offset must be an integer: ${options.offset}`;
111-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
111+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
112112
}
113113

114114
const mv = DefaultFunctions.number(ctx, {}, num);

mf2/icu-messageformat-1/src/validate.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { type Model as MF, MessageError, visit } from 'messageformat';
1+
import {
2+
type Model as MF,
3+
MessageFunctionError,
4+
MessageResolutionError,
5+
visit
6+
} from 'messageformat';
27
import { DefaultFunctions } from 'messageformat/functions';
38
import { MF1Functions } from './functions.ts';
49

510
/**
611
* Ensure that the `msg` data model does not contain any unsupported MF1 argType or argStyle references,
712
* calling `onError` on errors.
8-
* If `onError` is not defined, a {@link MessageError} will be thrown on error.
13+
* If `onError` is not defined, errors will be thrown.
914
*/
1015
export function mf1Validate(
1116
msg: MF.Message,
@@ -19,13 +24,18 @@ export function mf1Validate(
1924
? argTypeAttr.value
2025
: expr.functionRef!.name.replace('mf1:', '');
2126
if (type === 'unknown-function') {
22-
throw new MessageError(type, `Unsupported MF1 argType: ${argType}`);
27+
throw new MessageResolutionError(
28+
type,
29+
`Unsupported MF1 argType: ${argType}`,
30+
argType
31+
);
2332
} else {
2433
const opt = expr.functionRef!.options!.get('mf1:argStyle')!;
2534
const argStyle = opt?.type === 'literal' ? opt.value : '�';
26-
throw new MessageError(
35+
throw new MessageFunctionError(
2736
type,
28-
`Unsupported MF1 ${argType} argStyle: ${argStyle}`
37+
`Unsupported MF1 ${argType} argStyle: ${argStyle}`,
38+
argStyle
2939
);
3040
}
3141
}

mf2/messageformat/src/errors.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,8 @@ import { cstKey } from './data-model/from-cst.ts';
77
* @category Errors
88
*/
99
export class MessageError extends Error {
10-
type:
11-
| 'not-formattable'
12-
| 'unknown-function'
13-
| typeof MessageResolutionError.prototype.type
14-
| typeof MessageSelectionError.prototype.type
15-
| typeof MessageSyntaxError.prototype.type;
16-
17-
constructor(type: typeof MessageError.prototype.type, message: string) {
10+
type: string;
11+
constructor(type: string, message: string) {
1812
super(message);
1913
this.type = type;
2014
}
@@ -86,34 +80,43 @@ export class MessageDataModelError extends MessageSyntaxError {
8680
export class MessageResolutionError extends MessageError {
8781
declare type:
8882
| 'bad-function-result'
89-
| 'bad-operand'
90-
| 'bad-option'
91-
| 'unresolved-variable'
92-
| 'unsupported-operation';
83+
| 'bad-selector'
84+
| 'no-match'
85+
| 'unknown-function'
86+
| 'unresolved-variable';
9387
source: string;
88+
cause?: unknown;
9489
constructor(
9590
type: typeof MessageResolutionError.prototype.type,
9691
message: string,
97-
source: string
92+
source: string,
93+
cause?: unknown
9894
) {
9995
super(type, message);
10096
this.source = source;
97+
if (cause !== undefined) this.cause = cause;
10198
}
10299
}
103100

104101
/**
105-
* Errors in message selection.
102+
* Message runtime resolution errors
106103
*
107104
* @category Errors
108105
*/
109-
export class MessageSelectionError extends MessageError {
110-
declare type: 'bad-selector' | 'no-match';
111-
cause?: unknown;
106+
export class MessageFunctionError extends MessageError {
107+
declare type:
108+
| 'bad-operand'
109+
| 'bad-option'
110+
| 'bad-variant-key'
111+
| 'not-formattable'
112+
| 'unsupported-operation';
113+
source: string;
112114
constructor(
113-
type: typeof MessageSelectionError.prototype.type,
114-
cause?: unknown
115+
type: typeof MessageFunctionError.prototype.type,
116+
message: string,
117+
source: string
115118
) {
116-
super(type, `Selection error: ${type}`);
117-
if (cause !== undefined) this.cause = cause;
119+
super(type, message);
120+
this.source = source;
118121
}
119122
}

mf2/messageformat/src/functions/currency.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MessageError, MessageResolutionError } from '../errors.ts';
1+
import { MessageError, MessageFunctionError } from '../errors.ts';
22
import type { MessageFunctionContext } from './index.ts';
33
import type { MessageNumber, MessageNumberOptions } from './number.ts';
44
import { getMessageNumber, readNumericOperand } from './number.ts';
@@ -46,7 +46,7 @@ export function currency(
4646
const strval = asString(optval);
4747
if (strval === 'never') {
4848
ctx.onError(
49-
new MessageResolutionError(
49+
new MessageFunctionError(
5050
'unsupported-operation',
5151
'Currency display "never" is not yet supported',
5252
source
@@ -76,14 +76,14 @@ export function currency(
7676
ctx.onError(error);
7777
} else {
7878
const msg = `Value ${optval} is not valid for :currency option ${name}`;
79-
ctx.onError(new MessageResolutionError('bad-option', msg, source));
79+
ctx.onError(new MessageFunctionError('bad-option', msg, source));
8080
}
8181
}
8282
}
8383

8484
if (!options.currency) {
8585
const msg = 'A currency code is required for :currency';
86-
throw new MessageResolutionError('bad-operand', msg, source);
86+
throw new MessageFunctionError('bad-operand', msg, source);
8787
}
8888

8989
return getMessageNumber(ctx, input.value, options, false);

mf2/messageformat/src/functions/datetime.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getLocaleDir } from '../dir-utils.ts';
2-
import { MessageResolutionError } from '../errors.ts';
2+
import { MessageFunctionError } from '../errors.ts';
33
import type { MessageExpressionPart } from '../formatted-parts.ts';
44
import type { MessageValue } from '../message-value.ts';
55
import type { MessageFunctionContext } from '../resolve/function-context.ts';
@@ -84,15 +84,15 @@ export const datetime = (
8484
}
8585
} catch {
8686
const msg = `Value ${value} is not valid for :datetime ${name} option`;
87-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
87+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
8888
}
8989
}
9090
if (!hasStyle && !hasFields) {
9191
res.dateStyle = 'medium';
9292
res.timeStyle = 'short';
9393
} else if (hasStyle && hasFields) {
9494
const msg = 'Style and field options cannot be both set for :datetime';
95-
throw new MessageResolutionError('bad-option', msg, ctx.source);
95+
throw new MessageFunctionError('bad-option', msg, ctx.source);
9696
}
9797
});
9898

@@ -127,7 +127,7 @@ export const date = (
127127
}
128128
} catch {
129129
const msg = `Value ${value} is not valid for :date ${name} option`;
130-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
130+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
131131
}
132132
}
133133
res.dateStyle ??= 'medium';
@@ -164,7 +164,7 @@ export const time = (
164164
}
165165
} catch {
166166
const msg = `Value ${value} is not valid for :time ${name} option`;
167-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
167+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
168168
}
169169
}
170170
res.timeStyle ??= 'short';
@@ -196,7 +196,7 @@ function dateTimeImplementation(
196196
}
197197
if (!(value instanceof Date) || isNaN(value.getTime())) {
198198
const msg = 'Input is not a date';
199-
throw new MessageResolutionError('bad-operand', msg, ctx.source);
199+
throw new MessageFunctionError('bad-operand', msg, ctx.source);
200200
}
201201

202202
parseOptions(opt as Record<string, unknown>);

mf2/messageformat/src/functions/math.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MessageResolutionError } from '../errors.ts';
1+
import { MessageFunctionError } from '../errors.ts';
22
import type { MessageFunctionContext } from './index.ts';
33
import { MessageNumber, number, readNumericOperand } from './number.ts';
44
import { asPositiveInteger } from './utils.ts';
@@ -22,12 +22,12 @@ export function math(
2222
add = 'add' in exprOpt ? asPositiveInteger(exprOpt.add) : -1;
2323
sub = 'subtract' in exprOpt ? asPositiveInteger(exprOpt.subtract) : -1;
2424
} catch (error) {
25-
throw new MessageResolutionError('bad-option', String(error), source);
25+
throw new MessageFunctionError('bad-option', String(error), source);
2626
}
2727
if (add < 0 === sub < 0) {
2828
const msg =
2929
'Exactly one of "add" or "subtract" is required as a :math option';
30-
throw new MessageResolutionError('bad-option', msg, source);
30+
throw new MessageFunctionError('bad-option', msg, source);
3131
}
3232
const delta = add < 0 ? -sub : add;
3333
if (typeof value === 'number') value += delta;

mf2/messageformat/src/functions/number.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getLocaleDir } from '../dir-utils.ts';
2-
import { MessageResolutionError } from '../errors.ts';
2+
import { MessageFunctionError } from '../errors.ts';
33
import type { MessageExpressionPart } from '../formatted-parts.ts';
44
import type { MessageValue } from '../message-value.ts';
55
import type { MessageFunctionContext } from '../resolve/function-context.ts';
@@ -70,7 +70,7 @@ export function readNumericOperand(
7070
}
7171
if (typeof value !== 'bigint' && typeof value !== 'number') {
7272
const msg = 'Input is not numeric';
73-
throw new MessageResolutionError('bad-operand', msg, source);
73+
throw new MessageFunctionError('bad-operand', msg, source);
7474
}
7575
return { value, options };
7676
}
@@ -90,7 +90,7 @@ export function getMessageNumber(
9090
!ctx.literalOptionKeys.has('select')
9191
) {
9292
const msg = 'The option select may only be set by a literal value';
93-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
93+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
9494
canSelect = false;
9595
}
9696

@@ -180,7 +180,7 @@ export function number(
180180
}
181181
} catch {
182182
const msg = `Value ${optval} is not valid for :number option ${name}`;
183-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
183+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
184184
}
185185
}
186186

@@ -220,7 +220,7 @@ export function integer(
220220
}
221221
} catch {
222222
const msg = `Value ${optval} is not valid for :integer option ${name}`;
223-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
223+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
224224
}
225225
}
226226

mf2/messageformat/src/functions/test-functions.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* and are not a part of the library's public API.
66
*/
77

8-
import { MessageResolutionError } from '../errors.ts';
8+
import { MessageFunctionError } from '../errors.ts';
99
import type { MessageValue } from '../message-value.ts';
1010
import type { MessageFunctionContext } from '../resolve/function-context.ts';
1111
import { asPositiveInteger, asString } from './utils.ts';
@@ -60,7 +60,7 @@ function testFunction(
6060
}
6161
if (typeof input !== 'number') {
6262
const msg = 'Input is not numeric';
63-
throw new MessageResolutionError('bad-operand', msg, source);
63+
throw new MessageFunctionError('bad-operand', msg, source);
6464
}
6565
const value: number = input; // Otherwise TS complains in callbacks
6666

@@ -71,7 +71,7 @@ function testFunction(
7171
else throw 1;
7272
} catch {
7373
const msg = `Invalid option decimalPlaces=${options.decimalPlaces}`;
74-
throw new MessageResolutionError('bad-option', msg, source);
74+
throw new MessageFunctionError('bad-option', msg, source);
7575
}
7676
}
7777
if ('fails' in options) {
@@ -94,7 +94,7 @@ function testFunction(
9494
}
9595
} catch {
9696
const msg = `Invalid option fails=${options.fails}`;
97-
onError(new MessageResolutionError('bad-option', msg, source));
97+
onError(new MessageFunctionError('bad-option', msg, source));
9898
}
9999
}
100100

@@ -106,7 +106,10 @@ function testFunction(
106106
},
107107
selectKey: opt.canSelect
108108
? keys => {
109-
if (opt.failsSelect) throw new Error('Selection failed');
109+
if (opt.failsSelect) {
110+
const msg = 'Selection failed';
111+
throw new MessageFunctionError('bad-option', msg, source);
112+
}
110113
if (value === 1) {
111114
if (opt.decimalPlaces === 1 && keys.has('1.0')) return '1.0';
112115
if (keys.has('1')) return '1';
@@ -116,14 +119,20 @@ function testFunction(
116119
: undefined,
117120
toParts: opt.canFormat
118121
? () => {
119-
if (opt.failsFormat) throw new Error('Formatting failed');
122+
if (opt.failsFormat) {
123+
const msg = 'Formatting failed';
124+
throw new MessageFunctionError('bad-option', msg, source);
125+
}
120126
const parts = Array.from(testParts(value, opt.decimalPlaces));
121127
return [{ type: 'test', locale: 'und', parts }];
122128
}
123129
: undefined,
124130
toString: opt.canFormat
125131
? () => {
126-
if (opt.failsFormat) throw new Error('Formatting failed');
132+
if (opt.failsFormat) {
133+
const msg = 'Formatting failed';
134+
throw new MessageFunctionError('bad-option', msg, source);
135+
}
127136
const parts = Array.from(testParts(value, opt.decimalPlaces));
128137
return parts.map(part => part.value).join('');
129138
}

mf2/messageformat/src/functions/unit.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MessageError, MessageResolutionError } from '../errors.ts';
1+
import { MessageError, MessageFunctionError } from '../errors.ts';
22
import type { MessageFunctionContext } from './index.ts';
33
import type { MessageNumber, MessageNumberOptions } from './number.ts';
44
import { getMessageNumber, readNumericOperand } from './number.ts';
@@ -50,14 +50,14 @@ export function unit(
5050
ctx.onError(error);
5151
} else {
5252
const msg = `Value ${optval} is not valid for :currency option ${name}`;
53-
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
53+
ctx.onError(new MessageFunctionError('bad-option', msg, ctx.source));
5454
}
5555
}
5656
}
5757

5858
if (!options.unit) {
5959
const msg = 'A unit identifier is required for :unit';
60-
throw new MessageResolutionError('bad-operand', msg, ctx.source);
60+
throw new MessageFunctionError('bad-operand', msg, ctx.source);
6161
}
6262

6363
return getMessageNumber(ctx, input.value, options, false);

mf2/messageformat/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ export { visit } from './data-model/visit.ts';
2626
export {
2727
MessageDataModelError,
2828
MessageError,
29+
MessageFunctionError,
2930
MessageResolutionError,
30-
MessageSelectionError,
3131
MessageSyntaxError
3232
} from './errors.ts';
3333
export { MessageFormat, MessageFormatOptions } from './messageformat.ts';

0 commit comments

Comments
 (0)