diff --git a/fluent/src/builtins.js b/fluent/src/builtins.js index 3528bbcdf..7bfa8fd12 100644 --- a/fluent/src/builtins.js +++ b/fluent/src/builtins.js @@ -28,21 +28,25 @@ function values(opts) { export function NUMBER([arg], opts) { if (arg instanceof FluentNone) { - return arg; + return new FluentNone(`NUMBER(${arg.valueOf()})`); } + if (arg instanceof FluentNumber) { return new FluentNumber(arg.valueOf(), merge(arg.opts, opts)); } - return new FluentNone("NUMBER()"); + + throw new TypeError("Invalid argument type to NUMBER"); } export function DATETIME([arg], opts) { if (arg instanceof FluentNone) { - return arg; + return new FluentNone(`DATETIME(${arg.valueOf()})`); } + if (arg instanceof FluentDateTime) { return new FluentDateTime(arg.valueOf(), merge(arg.opts, opts)); } - return new FluentNone("DATETIME()"); + + throw new TypeError("Invalid argument type to DATETIME"); } diff --git a/fluent/src/resolver.js b/fluent/src/resolver.js index eb278c029..c16129a01 100644 --- a/fluent/src/resolver.js +++ b/fluent/src/resolver.js @@ -127,7 +127,7 @@ function resolveExpression(scope, expr) { function VariableReference(scope, {name}) { if (!scope.args || !scope.args.hasOwnProperty(name)) { if (scope.insideTermReference === false) { - scope.reportError(new ReferenceError(`Unknown variable: ${name}`)); + scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); } return new FluentNone(`$${name}`); } @@ -151,7 +151,7 @@ function VariableReference(scope, {name}) { } default: scope.reportError( - new TypeError(`Unsupported variable type: ${name}, ${typeof arg}`) + new TypeError(`Variable type not supported: $${name}, ${typeof arg}`) ); return new FluentNone(`$${name}`); } @@ -224,8 +224,8 @@ function FunctionReference(scope, {name, args}) { try { return func(...getArguments(scope, args)); - } catch (e) { - // XXX Report errors. + } catch (err) { + scope.reportError(err); return new FluentNone(`${name}()`); } } diff --git a/fluent/src/types.js b/fluent/src/types.js index a2805e3ca..ad379c8e1 100644 --- a/fluent/src/types.js +++ b/fluent/src/types.js @@ -45,12 +45,12 @@ export class FluentType { } export class FluentNone extends FluentType { - valueOf() { - return null; + constructor(value = "???") { + super(value); } toString() { - return `{${this.value || "???"}}`; + return `{${this.value}}`; } } @@ -65,8 +65,8 @@ export class FluentNumber extends FluentType { Intl.NumberFormat, this.opts ); return nf.format(this.value); - } catch (e) { - // XXX Report the error. + } catch (err) { + scope.reportError(err); return this.value; } } @@ -83,8 +83,8 @@ export class FluentDateTime extends FluentType { Intl.DateTimeFormat, this.opts ); return dtf.format(this.value); - } catch (e) { - // XXX Report the error. + } catch (err) { + scope.reportError(err); return this.value; } } diff --git a/fluent/test/functions_builtin_test.js b/fluent/test/functions_builtin_test.js index c72f361a8..905992d4f 100644 --- a/fluent/test/functions_builtin_test.js +++ b/fluent/test/functions_builtin_test.js @@ -25,20 +25,26 @@ suite('Built-in functions', function() { test('missing argument', function() { let msg; + errors = []; msg = bundle.getMessage('num-decimal'); - assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{NUMBER($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof ReferenceError); + assert.ok(errors[0] instanceof ReferenceError); + assert.strictEqual(errors[0].message, "Unknown variable: $arg"); + errors = []; msg = bundle.getMessage('num-percent'); - assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{NUMBER($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof ReferenceError); + assert.ok(errors[0] instanceof ReferenceError); + assert.strictEqual(errors[0].message, "Unknown variable: $arg"); + errors = []; msg = bundle.getMessage('num-bad-opt'); - assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{NUMBER($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof ReferenceError); + assert.ok(errors[0] instanceof ReferenceError); + assert.strictEqual(errors[0].message, "Unknown variable: $arg"); }); test('number argument', function() { @@ -55,66 +61,87 @@ suite('Built-in functions', function() { msg = bundle.getMessage('num-bad-opt'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '1'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof RangeError); // Invalid option value }); - // XXX Functions should report errors. - // https://github.com/projectfluent/fluent.js/issues/106 test('string argument', function() { const args = {arg: "Foo"}; let msg; + errors = []; msg = bundle.getMessage('num-decimal'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to NUMBER"); + errors = []; msg = bundle.getMessage('num-percent'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to NUMBER"); + errors = []; msg = bundle.getMessage('num-bad-opt'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to NUMBER"); }); - // XXX Functions should report errors. - // https://github.com/projectfluent/fluent.js/issues/106 test('date argument', function() { const date = new Date('2016-09-29'); const args = {arg: date}; let msg; + errors = []; msg = bundle.getMessage('num-decimal'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to NUMBER"); + errors = []; msg = bundle.getMessage('num-percent'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to NUMBER"); + errors = []; msg = bundle.getMessage('num-bad-opt'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to NUMBER"); }); test('invalid argument', function() { const args = {arg: []}; let msg; + errors = []; msg = bundle.getMessage('num-decimal'); - assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof TypeError); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Variable type not supported: $arg, object"); + errors = []; msg = bundle.getMessage('num-percent'); - assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof TypeError); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Variable type not supported: $arg, object"); + errors = []; msg = bundle.getMessage('num-bad-opt'); - assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{NUMBER($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof TypeError); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Variable type not supported: $arg, object"); }); }); @@ -131,20 +158,26 @@ suite('Built-in functions', function() { test('missing argument', function() { let msg; + errors = []; msg = bundle.getMessage('dt-default'); - assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{DATETIME($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof ReferenceError); + assert.ok(errors[0] instanceof ReferenceError); + assert.strictEqual(errors[0].message, "Unknown variable: $arg"); + errors = []; msg = bundle.getMessage('dt-month'); - assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{DATETIME($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof ReferenceError); + assert.ok(errors[0] instanceof ReferenceError); + assert.strictEqual(errors[0].message, "Unknown variable: $arg"); + errors = []; msg = bundle.getMessage('dt-bad-opt'); - assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, {}, errors), '{DATETIME($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof ReferenceError); + assert.ok(errors[0] instanceof ReferenceError); + assert.strictEqual(errors[0].message, "Unknown variable: $arg"); }); test('Date argument', function () { @@ -172,65 +205,86 @@ suite('Built-in functions', function() { // may vary depending on the TZ: // Thu Sep 29 2016 02:00:00 GMT+0200 (CEST) assert.strictEqual(bundle.formatPattern(msg.value, args, errors), date.toString()); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof RangeError); // Invalid option value }); - // XXX Functions should report errors. - // https://github.com/projectfluent/fluent.js/issues/106 test('number argument', function() { let args = {arg: 1}; let msg; + errors = []; msg = bundle.getMessage('dt-default'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to DATETIME"); + errors = []; msg = bundle.getMessage('dt-month'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to DATETIME"); + errors = []; msg = bundle.getMessage('dt-bad-opt'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to DATETIME"); }); - // XXX Functions should report errors. - // https://github.com/projectfluent/fluent.js/issues/106 test('string argument', function() { let args = {arg: 'Foo'}; let msg; + errors = []; msg = bundle.getMessage('dt-default'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to DATETIME"); + errors = []; msg = bundle.getMessage('dt-month'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to DATETIME"); + errors = []; msg = bundle.getMessage('dt-bad-opt'); assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME()}'); - assert.strictEqual(errors.length, 0); + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Invalid argument type to DATETIME"); }); test('invalid argument', function() { let args = {arg: []}; let msg; + errors = []; msg = bundle.getMessage('dt-default'); - assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof TypeError); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Variable type not supported: $arg, object"); + errors = []; msg = bundle.getMessage('dt-month'); - assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof TypeError); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Variable type not supported: $arg, object"); + errors = []; msg = bundle.getMessage('dt-bad-opt'); - assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{$arg}'); + assert.strictEqual(bundle.formatPattern(msg.value, args, errors), '{DATETIME($arg)}'); assert.strictEqual(errors.length, 1); - assert.ok(errors.pop() instanceof TypeError); + assert.ok(errors[0] instanceof TypeError); + assert.strictEqual(errors[0].message, "Variable type not supported: $arg, object"); }); }); });