Skip to content

Commit bb817cb

Browse files
authored
Use a Symbol for detecting instances of FluentType (#98)
Some bundlers make instanceof unreliable. Use a static FluentType.isTypeOf method to detect instances of FluentType via a $$typeof field and a 'FluentType' symbol.
1 parent cce4d42 commit bb817cb

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

fluent/src/resolver.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ function ExternalArgument(env, {name}) {
382382

383383
const arg = args[name];
384384

385-
if (arg instanceof FluentType) {
385+
// Return early if the argument already is an instance of FluentType.
386+
if (FluentType.isTypeOf(arg)) {
386387
return arg;
387388
}
388389

fluent/src/types.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,39 @@ export class FluentType {
4040
valueOf() {
4141
throw new Error('Subclasses of FluentType must implement valueOf.');
4242
}
43+
44+
/**
45+
* Internal field used for detecting instances of FluentType.
46+
*
47+
* @private
48+
*/
49+
get $$typeof() {
50+
return Symbol.for('FluentType');
51+
}
52+
53+
/**
54+
* Check if a value is an instance of FluentType.
55+
*
56+
* In some build/transpilation setups instanceof is unreliable for detecting
57+
* subclasses of FluentType. Instead, FluentType.isTypeOf uses the $$typeof
58+
* field and the FluentType Symbol to determine the type of the argument.
59+
*
60+
* @param {Any} obj - The value to check the type of.
61+
* @returns {bool}
62+
*/
63+
static isTypeOf(obj) {
64+
// The best-case scenario: the bundler didn't break the identity of
65+
// FluentType.
66+
if (obj instanceof FluentType) {
67+
return true;
68+
}
69+
70+
// Discard all primitive values, Object.prototype, and Object.create(null)
71+
// which by definition cannot be instances of FluentType. Then check the
72+
// value of the custom $$typeof field defined by the base FluentType class.
73+
return obj instanceof Object
74+
&& obj.$$typeof === Symbol.for('FluentType');
75+
}
4376
}
4477

4578
export class FluentNone extends FluentType {

fluent/test/arguments_test.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,11 @@ suite('External arguments', function() {
243243

244244
const parts = ctx.formatToParts(msg, args, errs);
245245
assert.equal(errs.length, 0);
246-
assert.deepEqual(parts, [args.arg]);
246+
247+
const [part] = parts;
248+
assert.equal(part, args.arg);
249+
assert.equal(part.$$typeof, Symbol.for('FluentType'));
250+
assert.equal(FluentType.isTypeOf(part), true);
247251

248252
const vals = parts.map(part => part.valueOf(ctx));
249253
assert.deepEqual(vals, [argval]);
@@ -254,7 +258,11 @@ suite('External arguments', function() {
254258

255259
const parts = ctx.formatToParts(msg, args, errs);
256260
assert.equal(errs.length, 0);
257-
assert.deepEqual(parts, [args.arg]);
261+
262+
const [part] = parts;
263+
assert.equal(part, args.arg);
264+
assert.equal(part.$$typeof, Symbol.for('FluentType'));
265+
assert.equal(FluentType.isTypeOf(part), true);
258266

259267
const vals = parts.map(part => part.valueOf(ctx));
260268
assert.deepEqual(vals, [argval]);

0 commit comments

Comments
 (0)