Skip to content

Commit

Permalink
remove defaulting of null and undefined to empty string
Browse files Browse the repository at this point in the history
This commit changes the algorithm for nested lookups to avoid throwing.
The new algorithm distinguishes failed lookups from successful lookups
which happen to result in null or undefined:

    > format('result: {foo.bar.baz}', null)
    'result: '

    > format('result: {foo.bar.baz}', {foo: {bar: {baz: null}}})
    'result: null'
  • Loading branch information
davidchambers committed May 18, 2018
1 parent 711dfc8 commit d174ab7
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 19 deletions.
36 changes: 17 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ void function(global) {
return err;
}

// defaultTo :: a,a? -> a
function defaultTo(x, y) {
return y == null ? x : y;
}

// create :: Object -> String,*... -> String
function create(transformers) {
return function(template) {
Expand Down Expand Up @@ -43,7 +38,23 @@ void function(global) {
key = String(idx);
idx += 1;
}
var value = defaultTo('', lookup(args, key.split('.')));

// 1. Split the key into a lookup path.
// 2. If the first path component is not an index, prepend '0'.
// 3. Reduce the lookup path to a single result. If the lookup
// succeeds the result is a singleton array containing the
// value at the lookup path; otherwise the result is [].
// 4. Unwrap the result by reducing with '' as the default value.
var path = key.split('.');
var value = (/^\d+$/.test(path[0]) ? path : ['0'].concat(path))
.reduce(function(maybe, key) {
return maybe.reduce(function(_, x) {
return x != null && key in Object(x) ?
[typeof x[key] === 'function' ? x[key]() : x[key]] :
[];
}, []);
}, [args])
.reduce(function(_, x) { return x; }, '');

if (xf == null) {
return value;
Expand All @@ -57,19 +68,6 @@ void function(global) {
};
}

function lookup(_obj, _path) {
var obj = _obj;
var path = _path;
if (!/^\d+$/.test(path[0])) {
path = ['0'].concat(path);
}
for (var idx = 0; idx < path.length; idx += 1) {
var key = path[idx];
obj = typeof obj[key] === 'function' ? obj[key]() : obj[key];
}
return obj;
}

// format :: String,*... -> String
var format = create({});

Expand Down
22 changes: 22 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ suite('format', function() {
);
});

test('uses default string representations', function() {
eq(format('result: {}', null), 'result: null');
eq(format('result: {}', undefined), 'result: undefined');
eq(format('result: {}', [1, 2, 3]), 'result: 1,2,3');
eq(format('result: {}', {foo: 42}), 'result: [object Object]');
});

test('treats "{{" and "}}" as "{" and "}"', function() {
eq(format('{{ {}: "{}" }}', 'foo', 'bar'), '{ foo: "bar" }');
});
Expand All @@ -75,6 +82,21 @@ suite('format', function() {
eq(format('{first} {last}', bobby), 'Bobby Fischer');
});

test('defaults to "" if lookup fails', function() {
eq(format('result: {foo.bar.baz}', null), 'result: ');
eq(format('result: {foo.bar.baz}', 'x'), 'result: ');
eq(format('result: {foo.bar.baz}', {}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: null}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: 'x'}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: {}}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: {bar: null}}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: {bar: 'x'}}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: {bar: {}}}), 'result: ');
eq(format('result: {foo.bar.baz}', {foo: {bar: {baz: null}}}), 'result: null');
eq(format('result: {foo.bar.baz}', {foo: {bar: {baz: 'x'}}}), 'result: x');
eq(format('result: {foo.bar.baz}', {foo: {bar: {baz: {}}}}), 'result: [object Object]');
});

test('invokes methods', function() {
eq(format('{0.toLowerCase}', 'III'), 'iii');
eq(format('{0.toUpperCase}', 'iii'), 'III');
Expand Down

0 comments on commit d174ab7

Please sign in to comment.