diff --git a/index.js b/index.js index 4b7c9ef..e0da636 100644 --- a/index.js +++ b/index.js @@ -5,11 +5,11 @@ function Traverse (obj) { } Traverse.prototype.map = function (cb) { - return walk(this.clone(), cb); + return walk(this.value, cb, true); }; Traverse.prototype.forEach = function (cb) { - this.value = walk(this.value, cb); + this.value = walk(this.value, cb, false); return this.value; }; @@ -89,9 +89,10 @@ Traverse.prototype.clone = function () { })(this.value); }; -function walk (root, cb) { +function walk (root, cb, immutable) { var path = []; var parents = []; + if (immutable) root = copy(root); (function walker (node) { var modifiers = {}; @@ -148,7 +149,7 @@ function walk (root, cb) { state.notRoot = !state.isRoot; // use return values to update if defined - var ret = cb.call(state, node); + var ret = cb.call(state, state.node); if (ret !== undefined && state.update) state.update(ret); if (modifiers.before) modifiers.before.call(state, state.node); @@ -162,7 +163,11 @@ function walk (root, cb) { if (modifiers.pre) modifiers.pre.call(state, state.node[key], key); - var child = walker(state.node[key]); + var child = walker( + immutable ? copy(state.node[key]) : state.node[key] + ); + if (immutable) state.node[child.key] = child.node; + child.isLast = i == keys.length - 1; child.isFirst = i == 0; @@ -188,3 +193,17 @@ Object.keys(Traverse.prototype).forEach(function (key) { return t[key].apply(t, args); }; }); + +function copy (node) { + if (Array.isArray(node)) { + return node.slice(); + } + else if (typeof node === 'object') { + var cp = Object.create(node.__proto__); + Object.keys(node).forEach(function (key) { + cp[key] = node[key]; + }); + return cp; + } + else return node; +} diff --git a/test/circular.js b/test/circular.js index 0ec6a5f..08c0b73 100644 --- a/test/circular.js +++ b/test/circular.js @@ -25,13 +25,13 @@ exports.deepCirc = function () { var times = 0; Traverse(obj).forEach(function (x) { if (this.circular) { - assert.eql(this.circular.path, []); - assert.eql(this.path, [ 'y', 2 ]); + assert.deepEqual(this.circular.path, []); + assert.deepEqual(this.path, [ 'y', 2 ]); times ++; } }); - assert.eql(times, 1); + assert.deepEqual(times, 1); }; exports.doubleCirc = function () { @@ -46,13 +46,13 @@ exports.doubleCirc = function () { } }); - assert.eql(circs[0].self.path, [ 'x', 3, 2 ]); - assert.eql(circs[0].circ.path, []); + assert.deepEqual(circs[0].self.path, [ 'x', 3, 2 ]); + assert.deepEqual(circs[0].circ.path, []); - assert.eql(circs[1].self.path, [ 'y', 2 ]); - assert.eql(circs[1].circ.path, []); + assert.deepEqual(circs[1].self.path, [ 'y', 2 ]); + assert.deepEqual(circs[1].circ.path, []); - assert.eql(circs.length, 2); + assert.deepEqual(circs.length, 2); }; exports.circDubForEach = function () { @@ -64,7 +64,7 @@ exports.circDubForEach = function () { if (this.circular) this.update('...'); }); - assert.eql(obj, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] }); + assert.deepEqual(obj, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] }); }; exports.circDubMap = function () { @@ -78,7 +78,7 @@ exports.circDubMap = function () { } }); - assert.eql(c, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] }); + assert.deepEqual(c, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] }); }; exports.circClone = function () { @@ -93,6 +93,6 @@ exports.circClone = function () { assert.ok(clone.y[2] !== obj); assert.ok(clone.x[3][2] === clone); assert.ok(clone.x[3][2] !== obj); - assert.eql(clone.x.slice(0,3), [1,2,3]); - assert.eql(clone.y.slice(0,2), [4,5]); + assert.deepEqual(clone.x.slice(0,3), [1,2,3]); + assert.deepEqual(clone.y.slice(0,2), [4,5]); }; diff --git a/test/date.js b/test/date.js index 72630e7..2cb8252 100644 --- a/test/date.js +++ b/test/date.js @@ -11,7 +11,7 @@ exports.dateEach = function () { counts[t] = (counts[t] || 0) + 1; }); - assert.eql(counts, { + assert.deepEqual(counts, { object : 1, Date : 1, number : 2, @@ -26,7 +26,7 @@ exports.dateMap = function () { }); assert.ok(obj.x !== res.x); - assert.eql(res, { + assert.deepEqual(res, { x : obj.x, y : 110, z : 105, diff --git a/test/interface.js b/test/interface.js index f16fe53..df5b037 100644 --- a/test/interface.js +++ b/test/interface.js @@ -4,7 +4,7 @@ var Traverse = require('traverse'); exports['interface map'] = function () { var obj = { a : [ 5,6,7 ], b : { c : [8] } }; - assert.eql( + assert.deepEqual( Traverse.paths(obj) .sort() .map(function (path) { return path.join('/') }) @@ -14,7 +14,7 @@ exports['interface map'] = function () { 'a a/0 a/1 a/2 b b/c b/c/0' ); - assert.eql( + assert.deepEqual( Traverse.nodes(obj), [ { a: [ 5, 6, 7 ], b: { c: [ 8 ] } }, @@ -23,7 +23,7 @@ exports['interface map'] = function () { ] ); - assert.eql( + assert.deepEqual( Traverse.map(obj, function (node) { if (typeof node == 'number') { return node + 1000; @@ -37,6 +37,6 @@ exports['interface map'] = function () { var nodes = 0; Traverse.forEach(obj, function (node) { nodes ++ }); - assert.eql(nodes, 8); + assert.deepEqual(nodes, 8); }; diff --git a/test/json.js b/test/json.js index 0b067cb..bf36620 100644 --- a/test/json.js +++ b/test/json.js @@ -24,22 +24,22 @@ exports['json test'] = function () { 'obj.foo[3] replaced with "[Function]"' ); - assert.eql(scrubbed, { + assert.deepEqual(scrubbed, { moo : '[Function]', foo : [ 2, 3, 4, "[Function]" ] }, 'Full JSON string matches'); - assert.eql( + assert.deepEqual( typeof obj.moo, 'function', 'Original obj.moo still a function' ); - assert.eql( + assert.deepEqual( typeof obj.foo[3], 'function', 'Original obj.foo[3] still a function' ); - assert.eql(callbacks, { + assert.deepEqual(callbacks, { 54: { id: 54, f : obj.moo, path: [ 'moo' ] }, 55: { id: 55, f : obj.foo[3], path: [ 'foo', '3' ] }, }, 'Check the generated callbacks list'); diff --git a/test/mutability.js b/test/mutability.js index 8a6fa13..58dea6a 100644 --- a/test/mutability.js +++ b/test/mutability.js @@ -8,8 +8,8 @@ exports.mutate = function () { this.update(x * 10); } }); - assert.eql(obj, res); - assert.eql(obj, { a : 1, b : 20, c : [ 3, 40 ] }); + assert.deepEqual(obj, res); + assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] }); }; exports.mutateT = function () { @@ -19,8 +19,8 @@ exports.mutateT = function () { this.update(x * 10); } }); - assert.eql(obj, res); - assert.eql(obj, { a : 1, b : 20, c : [ 3, 40 ] }); + assert.deepEqual(obj, res); + assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] }); }; exports.map = function () { @@ -30,8 +30,8 @@ exports.map = function () { this.update(x * 10); } }); - assert.eql(obj, { a : 1, b : 2, c : [ 3, 4 ] }); - assert.eql(res, { a : 1, b : 20, c : [ 3, 40 ] }); + assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] }); + assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] }); }; exports.mapT = function () { @@ -41,30 +41,30 @@ exports.mapT = function () { this.update(x * 10); } }); - assert.eql(obj, { a : 1, b : 2, c : [ 3, 4 ] }); - assert.eql(res, { a : 1, b : 20, c : [ 3, 40 ] }); + assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] }); + assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] }); }; exports.clone = function () { var obj = { a : 1, b : 2, c : [ 3, 4 ] }; var res = Traverse(obj).clone(); - assert.eql(obj, res); + assert.deepEqual(obj, res); assert.ok(obj !== res); obj.a ++; - assert.eql(res.a, 1); + assert.deepEqual(res.a, 1); obj.c.push(5); - assert.eql(res.c, [ 3, 4 ]); + assert.deepEqual(res.c, [ 3, 4 ]); }; exports.cloneT = function () { var obj = { a : 1, b : 2, c : [ 3, 4 ] }; var res = Traverse.clone(obj); - assert.eql(obj, res); + assert.deepEqual(obj, res); assert.ok(obj !== res); obj.a ++; - assert.eql(res.a, 1); + assert.deepEqual(res.a, 1); obj.c.push(5); - assert.eql(res.c, [ 3, 4 ]); + assert.deepEqual(res.c, [ 3, 4 ]); }; exports.reduce = function () { @@ -73,8 +73,8 @@ exports.reduce = function () { if (this.isLeaf) acc.push(x); return acc; }, []); - assert.eql(obj, { a : 1, b : 2, c : [ 3, 4 ] }); - assert.eql(res, [ 1, 2, 3, 4 ]); + assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] }); + assert.deepEqual(res, [ 1, 2, 3, 4 ]); }; exports.reduceInit = function () { @@ -83,6 +83,6 @@ exports.reduceInit = function () { if (this.isRoot) assert.fail('got root'); return acc; }); - assert.eql(obj, { a : 1, b : 2, c : [ 3, 4 ] }); - assert.eql(res, obj); + assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] }); + assert.deepEqual(res, obj); }; diff --git a/test/negative.js b/test/negative.js index 706332a..6cf287d 100644 --- a/test/negative.js +++ b/test/negative.js @@ -7,12 +7,12 @@ exports['negative update test'] = function () { if (x < 0) this.update(x + 128); }); - assert.eql(fixed, + assert.deepEqual(fixed, [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ], 'Negative values += 128' ); - assert.eql(obj, + assert.deepEqual(obj, [ 5, 6, -3, [ 7, 8, -2, 1 ], { f: 10, g: -13 } ], 'Original references not modified' );