diff --git a/.eslintrc b/.eslintrc index 30de5dd..4729eb6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,6 +19,7 @@ "no-invalid-this": 0, "object-curly-newline": 0, "sort-keys": 0, + "max-lines": "warn", }, "overrides": [ diff --git a/index.js b/index.js index ce507da..3788990 100644 --- a/index.js +++ b/index.js @@ -66,7 +66,7 @@ function isWritable(object, key) { return !gopd(object, key).writable; } -function copy(src) { +function copy(src, options) { if (typeof src === 'object' && src !== null) { var dst; @@ -98,7 +98,8 @@ function copy(src) { } } - forEach(ownEnumerableKeys(src), function (key) { + var iteratorFunction = options.includeSymbols ? ownEnumerableKeys : objectKeys; + forEach(iteratorFunction(src), function (key) { dst[key] = src[key]; }); return dst; @@ -106,6 +107,7 @@ function copy(src) { return src; } +/** @type {TraverseOptions} */ var emptyNull = { __proto__: null }; function walk(root, cb) { @@ -113,10 +115,11 @@ function walk(root, cb) { var parents = []; var alive = true; var options = arguments.length > 2 ? arguments[2] : emptyNull; + var iteratorFunction = options.includeSymbols ? ownEnumerableKeys : objectKeys; var immutable = !!options.immutable; return (function walker(node_) { - var node = immutable ? copy(node_) : node_; + var node = immutable ? copy(node_, options) : node_; var modifiers = {}; var keepGoing = true; @@ -164,7 +167,7 @@ function walk(root, cb) { function updateState() { if (typeof state.node === 'object' && state.node !== null) { if (!state.keys || state.node_ !== state.node) { - state.keys = ownEnumerableKeys(state.node); + state.keys = iteratorFunction(state.node); } state.isLeaf = state.keys.length === 0; @@ -233,15 +236,29 @@ function walk(root, cb) { }(root)).node; } +/** @typedef {{ immutable?: boolean, includeSymbols?: boolean }} TraverseOptions */ + +/** + * A traverse constructor + * @param {object} obj - the object to traverse + * @param {TraverseOptions | undefined} [options] - options for the traverse + * @constructor + */ function Traverse(obj) { + /** @type {TraverseOptions} */ + this.options = arguments.length > 1 ? arguments[1] : emptyNull; this.value = obj; } +/** @type {(ps: PropertyKey[]) => Traverse['value']} */ Traverse.prototype.get = function (ps) { var node = this.value; - for (var i = 0; i < ps.length; i++) { + for (var i = 0; node && i < ps.length; i++) { var key = ps[i]; - if (!node || !hasOwnProperty.call(node, key)) { + if ( + !hasOwnProperty.call(node, key) + || (!this.options.includeSymbols && typeof key === 'symbol') + ) { return void undefined; } node = node[key]; @@ -249,11 +266,12 @@ Traverse.prototype.get = function (ps) { return node; }; +/** @type {(ps: PropertyKey[]) => boolean} */ Traverse.prototype.has = function (ps) { var node = this.value; - for (var i = 0; i < ps.length; i++) { + for (var i = 0; node && i < ps.length; i++) { var key = ps[i]; - if (!node || !hasOwnProperty.call(node, key)) { + if (!hasOwnProperty.call(node, key) || (!this.options.includeSymbols && typeof key === 'symbol')) { return false; } node = node[key]; @@ -272,14 +290,12 @@ Traverse.prototype.set = function (ps, value) { return value; }; -var immutableOpts = { __proto__: null, immutable: true }; - Traverse.prototype.map = function (cb) { - return walk(this.value, cb, immutableOpts); + return walk(this.value, cb, { __proto__: null, immutable: true, includeSymbols: !!this.options.includeSymbols }); }; Traverse.prototype.forEach = function (cb) { - this.value = walk(this.value, cb); + this.value = walk(this.value, cb, this.options); return this.value; }; @@ -313,6 +329,7 @@ Traverse.prototype.nodes = function () { Traverse.prototype.clone = function () { var parents = []; var nodes = []; + var options = this.options; if (whichTypedArray(this.value)) { return taSlice(this.value); @@ -326,12 +343,13 @@ Traverse.prototype.clone = function () { } if (typeof src === 'object' && src !== null) { - var dst = copy(src); + var dst = copy(src, options); parents.push(src); nodes.push(dst); - forEach(ownEnumerableKeys(src), function (key) { + var iteratorFunction = options.includeSymbols ? ownEnumerableKeys : objectKeys; + forEach(iteratorFunction(src), function (key) { dst[key] = clone(src[key]); }); @@ -345,8 +363,10 @@ Traverse.prototype.clone = function () { }(this.value)); }; +/** @type {(obj: object, options?: TraverseOptions) => Traverse} */ function traverse(obj) { - return new Traverse(obj); + var options = arguments.length > 1 ? arguments[1] : emptyNull; + return new Traverse(obj, options); } // TODO: replace with object.assign? diff --git a/test/has.js b/test/has.js index 6802a11..fc65a0c 100644 --- a/test/has.js +++ b/test/has.js @@ -23,14 +23,29 @@ test('has', function (t) { obj[globalSymbol][localSymbol] = 7; obj[localSymbol] = 8; - st.equal(traverse(obj).has([globalSymbol]), true); + st.equal(traverse(obj).has([globalSymbol]), false); + st.equal(traverse(obj, { includeSymbols: true }).has([globalSymbol]), true); + st.equal(traverse(obj).has([globalSymbol, globalSymbol]), false); - st.equal(traverse(obj).has([globalSymbol, localSymbol]), true); - st.equal(traverse(obj).has([localSymbol]), true); - st.equal(traverse(obj).has([localSymbol]), true); + st.equal(traverse(obj, { includeSymbols: true }).has([globalSymbol, globalSymbol]), false); + + st.equal(traverse(obj).has([globalSymbol, localSymbol]), false); + st.equal(traverse(obj, { includeSymbols: true }).has([globalSymbol, localSymbol]), true); + + st.equal(traverse(obj).has([localSymbol]), false); + st.equal(traverse(obj, { includeSymbols: true }).has([localSymbol]), true); + + st.equal(traverse(obj).has([Symbol('d')]), false); + st.equal(traverse(obj, { includeSymbols: true }).has([Symbol('d')]), false); + st.equal(traverse(obj).has([Symbol('e')]), false); - st.equal(traverse(obj).has([Symbol.for('d')]), true); + st.equal(traverse(obj, { includeSymbols: true }).has([Symbol('e')]), false); + + st.equal(traverse(obj).has([Symbol.for('d')]), false); + st.equal(traverse(obj, { includeSymbols: true }).has([Symbol.for('d')]), true); + st.equal(traverse(obj).has([Symbol.for('e')]), false); + st.equal(traverse(obj, { includeSymbols: true }).has([Symbol.for('e')]), false); st.end(); });