Skip to content

Commit

Permalink
Catch errors thrown by Intl formatters
Browse files Browse the repository at this point in the history
Intl formatters may throw errors when constructed (if unrecognized
options are passed) or in format() if the formatted value is not
supported.

This is a simple fix which simply catches those errors.  The real fix
should report them, for instance into env.errors, but it's not clear to
me right now how to do this right.
  • Loading branch information
stasm committed Oct 17, 2017
1 parent 13f30bf commit 1b7837f
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 35 deletions.
15 changes: 11 additions & 4 deletions fluent/src/builtins.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import { FluentNumber, FluentDateTime } from './types';

export default {
'NUMBER': ([arg], opts) =>
new FluentNumber(arg.value, merge(arg.opts, opts)),
new FluentNumber(value(arg), merge(arg.opts, opts)),
'DATETIME': ([arg], opts) =>
new FluentDateTime(arg.value, merge(arg.opts, opts)),
new FluentDateTime(value(arg), merge(arg.opts, opts)),
};

function merge(argopts, opts) {
Expand All @@ -26,8 +26,15 @@ function merge(argopts, opts) {

function values(opts) {
const unwrapped = {};
for (const name of Object.keys(opts)) {
unwrapped[name] = opts[name].value;
for (const [name, opt] of Object.entries(opts)) {
unwrapped[name] = value(opt);
}
return unwrapped;
}

function value(arg) {
// StringExpression-typed options are parsed as regular strings by the
// runtime parser and are not converted to a FluentType by the resolver.
// They don't have the "value" property; they are the value.
return typeof arg === 'string' ? arg : arg.value;
}
10 changes: 7 additions & 3 deletions fluent/src/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ function CallExpression(env, {fun, args}) {
}

const posargs = [];
const keyargs = [];
const keyargs = {};

for (const arg of args) {
if (arg.type === 'narg') {
Expand All @@ -467,8 +467,12 @@ function CallExpression(env, {fun, args}) {
}
}

// XXX functions should also report errors
return callee(posargs, keyargs);
try {
return callee(posargs, keyargs);
} catch (e) {
// XXX Report errors.
return FluentNone();
}
}

/**
Expand Down
26 changes: 18 additions & 8 deletions fluent/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,15 @@ export class FluentNumber extends FluentType {
}

valueOf(ctx) {
const nf = ctx._memoizeIntlObject(
Intl.NumberFormat, this.opts
);
return nf.format(this.value);
try {
const nf = ctx._memoizeIntlObject(
Intl.NumberFormat, this.opts
);
return nf.format(this.value);
} catch (e) {
// XXX Report the error.
return this.value;
}
}

/**
Expand All @@ -81,10 +86,15 @@ export class FluentDateTime extends FluentType {
}

valueOf(ctx) {
const dtf = ctx._memoizeIntlObject(
Intl.DateTimeFormat, this.opts
);
return dtf.format(this.value);
try {
const dtf = ctx._memoizeIntlObject(
Intl.DateTimeFormat, this.opts
);
return dtf.format(this.value);
} catch (e) {
// XXX Report the error.
return this.value;
}
}
}

Expand Down
100 changes: 80 additions & 20 deletions fluent/test/functions_builtin_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,106 @@ import { MessageContext } from '../src/context';
import { ftl } from './util';

suite('Built-in functions', function() {
let ctx, args, errs;

setup(function() {
errs = [];
});
let ctx;

suite('NUMBER', function(){
suiteSetup(function() {
ctx = new MessageContext('en-US', { useIsolating: false });
ctx.addMessages(ftl`
foo = { NUMBER(1) }
num-decimal = { NUMBER($arg) }
num-percent = { NUMBER($arg, style: "percent") }
num-bad-opt = { NUMBER($arg, style: "bad") }
`);
});

test('formats the number', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, args, errs);
assert.equal(val, '1');
assert.equal(errs.length, 0);
test('missing argument', function() {
let msg;

msg = ctx.getMessage('num-decimal');
assert.equal(ctx.format(msg), 'NaN');

msg = ctx.getMessage('num-percent');
assert.equal(ctx.format(msg), 'NaN');

msg = ctx.getMessage('num-bad-opt');
assert.equal(ctx.format(msg), 'NaN');
});

test('number argument', function() {
const args = {arg: 1};
let msg;

msg = ctx.getMessage('num-decimal');
assert.equal(ctx.format(msg, args), '1');

msg = ctx.getMessage('num-percent');
assert.equal(ctx.format(msg, args), '100%');

msg = ctx.getMessage('num-bad-opt');
assert.equal(ctx.format(msg, args), '1');
});

test('string argument', function() {
const args = {arg: "Foo"};
let msg;

msg = ctx.getMessage('num-decimal');
assert.equal(ctx.format(msg, args), 'NaN');

msg = ctx.getMessage('num-percent');
assert.equal(ctx.format(msg, args), 'NaN');

msg = ctx.getMessage('num-bad-opt');
assert.equal(ctx.format(msg, args), 'NaN');
});
});

suite('DATETIME', function(){
let dtf;

suiteSetup(function() {
dtf = new Intl.DateTimeFormat('en-US');
ctx = new MessageContext('en-US', { useIsolating: false });
ctx.addMessages(ftl`
foo = { DATETIME($date) }
dt-default = { DATETIME($arg) }
dt-month = { DATETIME($arg, month: "long") }
dt-bad-opt = { DATETIME($arg, month: "bad") }
`);
});

test('formats the date', function() {
test('missing argument', function() {
let msg;

msg = ctx.getMessage('dt-default');
assert.equal(ctx.format(msg), 'Invalid Date');

msg = ctx.getMessage('dt-month');
assert.equal(ctx.format(msg), 'Invalid Date');

msg = ctx.getMessage('dt-bad-opt');
assert.equal(ctx.format(msg), 'Invalid Date');
});

test.only('Date argument', function() {
const date = new Date('2016-09-29');
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { date }, errs);
// format the date argument to account for the testrunner's timezone
assert.equal(val, dtf.format(date));
assert.equal(errs.length, 0);
const expectedDefault =
(new Intl.DateTimeFormat('en-US')).format(date);
const expectedMonth =
(new Intl.DateTimeFormat('en-US', {month: 'long'})).format(date);

const args = {arg: date};
let msg;

msg = ctx.getMessage('dt-default');
assert.equal(ctx.format(msg, args), expectedDefault);

msg = ctx.getMessage('dt-month');
assert.equal(ctx.format(msg, args), expectedMonth);

msg = ctx.getMessage('dt-bad-opt');
// The argument value will be coerced into a string by the join operation
// in MessageContext.format. The result looks something like this; it
// may vary depending on the TZ:
// Thu Sep 29 2016 02:00:00 GMT+0200 (CEST)
assert.equal(ctx.format(msg, args), date.toString());
});
});
});

0 comments on commit 1b7837f

Please sign in to comment.