diff --git a/package.json b/package.json index 0fbdf1d..200c9d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neotraverse", - "version": "0.6.9", + "version": "0.0.1", "description": "traverse and transform objects by visiting every node on a recursive walk", "main": "dist/index.js", "type": "module", diff --git a/src/index.ts b/src/index.ts index 8e8f9f6..907e9ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,226 +1,331 @@ -import { TraverseOptions } from '..'; - -// var whichTypedArray = require('which-typed-array'); -var taSlice = (v) => v.slice(); +type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +export interface TraverseOptions { + /** + * If true, does not alter the original object + */ + immutable?: boolean; + + /** + * If false, removes all symbols from traversed objects + * + * @default false + */ + includeSymbols?: boolean; +} -function isTypedArray(value) { - return ArrayBuffer.isView(value) && !(value instanceof DataView); +export interface TraverseContext { + /** + * The present node on the recursive walk + */ + node: any; + + /** + * An array of string keys from the root to the present node + */ + path: string[]; + + /** + * The context of the node's parent. + * This is `undefined` for the root node. + */ + parent: TraverseContext | undefined; + + /** + * The contexts of the node's parents. + */ + parents: TraverseContext[]; + + /** + * The name of the key of the present node in its parent. + * This is `undefined` for the root node. + */ + key: PropertyKey | undefined; + + /** + * Whether the present node is the root node + */ + isRoot: boolean; + /** + * Whether the present node is not the root node + */ + notRoot: boolean; + + /** + * Whether the present node is the last node + */ + isLast: boolean; + + /** + * Whether the present node is the first node + */ + isFirst: boolean; + + /** + * Whether or not the present node is a leaf node (has no children) + */ + isLeaf: boolean; + /** + * Whether or not the present node is not a leaf node (has children) + */ + notLeaf: boolean; + + /** + * Depth of the node within the traversal + */ + level: number; + + /** + * If the node equals one of its parents, the `circular` attribute is set to the context of that parent and the traversal progresses no deeper. + */ + circular: TraverseContext | undefined; + + /** + * Set a new value for the present node. + * + * All the elements in `value` will be recursively traversed unless `stopHere` is true (false by default). + */ + update(value: any, stopHere?: boolean): void; + + /** + * Remove the current element from the output. If the node is in an Array it will be spliced off. Otherwise it will be deleted from its parent. + */ + remove(stopHere?: boolean): void; + + /** + * Delete the current element from its parent in the output. Calls `delete` even on Arrays. + */ + delete(stopHere?: boolean): void; + + /** + * Object keys of the node. + */ + keys: PropertyKey[] | null; + + /** + * Call this function before all of the children are traversed. + * You can assign into `this.keys` here to traverse in a custom order. + */ + before(callback: (this: TraverseContext, value: any) => void): void; + + /** + * Call this function after all of the children are traversed. + */ + after(callback: (this: TraverseContext, value: any) => void): void; + + /** + * Call this function before each of the children are traversed. + */ + pre(callback: (this: TraverseContext, child: any, key: any) => void): void; + + /** + * Call this function after each of the children are traversed. + */ + post(callback: (this: TraverseContext, child: any) => void): void; + + /** + * Stops traversal entirely. + */ + stop(): void; + + /** + * Prevents traversing descendents of the current node. + */ + block(): void; } -const gopd = Object.getOwnPropertyDescriptor; +type InternalTraverseOptions = TraverseOptions & { __proto__: null }; -// TODO: use call-bind, is-date, is-regex, is-string, is-boolean-object, is-number-object -function toS(obj) { - return Object.prototype.toString.call(obj); -} -function isDate(obj) { - return toS(obj) === '[object Date]'; -} -function isRegExp(obj) { - return toS(obj) === '[object RegExp]'; -} -function isError(obj) { - return toS(obj) === '[object Error]'; -} -function isBoolean(obj) { - return toS(obj) === '[object Boolean]'; -} -function isNumber(obj) { - return toS(obj) === '[object Number]'; -} -function isString(obj) { - return toS(obj) === '[object String]'; -} +const to_string = (obj: unknown) => Object.prototype.toString.call(obj); -// TODO: use isarray -var isArray = - Array.isArray || - function isArray(xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; - }; - -// TODO: use for-each? -function forEach(xs, fn) { - if (xs.forEach) { - return xs.forEach(fn); - } - for (var i = 0; i < xs.length; i++) { - fn(xs[i], i, xs); - } - return void undefined; -} +const is_typed_array = (value: unknown): value is TypedArray => + ArrayBuffer.isView(value) && !(value instanceof DataView); +const is_date = (obj: unknown): obj is Date => to_string(obj) === '[object Date]'; +const is_regexp = (obj: unknown): obj is RegExp => to_string(obj) === '[object RegExp]'; +const is_error = (obj: unknown): obj is Error => to_string(obj) === '[object Error]'; +const is_boolean = (obj: unknown): obj is boolean => to_string(obj) === '[object Boolean]'; +const is_number = (obj: unknown): obj is number => to_string(obj) === '[object Number]'; +const is_string = (obj: unknown): obj is string => to_string(obj) === '[object String]'; +const is_array = Array.isArray; -// TODO: use object-keys -var objectKeys = - Object.keys || - function keys(obj) { - var res = []; - for (var key in obj) { - res.push(key); - } // eslint-disable-line no-restricted-syntax - return res; - }; - -var propertyIsEnumerable = Object.prototype.propertyIsEnumerable; -var getOwnPropertySymbols = Object.getOwnPropertySymbols; // eslint-disable-line id-length - -// TODO: use reflect.ownkeys and filter out non-enumerables -function ownEnumerableKeys(obj) { - var res = objectKeys(obj); - - // Include enumerable symbol properties. - if (getOwnPropertySymbols) { - var symbols = getOwnPropertySymbols(obj); - for (var i = 0; i < symbols.length; i++) { - if (propertyIsEnumerable.call(obj, symbols[i])) { - res.push(symbols[i]); - } +const gopd = Object.getOwnPropertyDescriptor; +const is_property_enumerable = Object.prototype.propertyIsEnumerable; +const get_own_property_symbols = Object.getOwnPropertySymbols; +const has_own_property = Object.prototype.hasOwnProperty; + +function own_enumerable_keys(obj: object): PropertyKey[] { + const res: PropertyKey[] = Object.keys(obj); + + const symbols = get_own_property_symbols(obj); + for (let i = 0; i < symbols.length; i++) { + if (is_property_enumerable.call(obj, symbols[i])) { + res.push(symbols[i]); } } + return res; } -// TODO: use object.hasown -var hasOwnProperty = - Object.prototype.hasOwnProperty || - function (obj, key) { - return key in obj; - }; - -function isWritable(object, key) { - if (typeof gopd !== 'function') { - return true; - } - - return !gopd(object, key).writable; +function is_writable(object, key) { + return !gopd(object, key)?.writable; } -function copy(src, options) { +function copy(src: any, options) { if (typeof src === 'object' && src !== null) { - var dst; + let dst: any; - if (isArray(src)) { + if (is_array(src)) { dst = []; - } else if (isDate(src)) { + } else if (is_date(src)) { dst = new Date(src.getTime ? src.getTime() : src); - } else if (isRegExp(src)) { + console.log({ src, dst }); + } else if (is_regexp(src)) { dst = new RegExp(src); - } else if (isError(src)) { + } else if (is_error(src)) { dst = { message: src.message }; - } else if (isBoolean(src) || isNumber(src) || isString(src)) { + } else if (is_boolean(src) || is_number(src) || is_string(src)) { dst = Object(src); } else { - var ta = isTypedArray(src); - if (ta) { - return taSlice(src); + if (is_typed_array(src)) { + return src.slice(); } else if (Object.create && Object.getPrototypeOf) { dst = Object.create(Object.getPrototypeOf(src)); } else if (src.constructor === Object) { dst = {}; } else { - var proto = (src.constructor && src.constructor.prototype) || src.__proto__ || {}; - var T = function T() {}; // eslint-disable-line func-style, func-name-matching + const proto = (src.constructor && src.constructor.prototype) || src.__proto__ || {}; + const T = function T() {}; // eslint-disable-line func-style, func-name-matching T.prototype = proto; dst = new T(); } } - var iteratorFunction = options.includeSymbols ? ownEnumerableKeys : objectKeys; - forEach(iteratorFunction(src), function (key) { + const iterator_function = options.includeSymbols ? own_enumerable_keys : Object.keys; + for (const key of iterator_function(src)) { dst[key] = src[key]; - }); + } + return dst; } + return src; } -/** @type {TraverseOptions} */ -var emptyNull = { __proto__: null }; +const empty_null: InternalTraverseOptions = { + __proto__: null, + includeSymbols: false, + immutable: false, +}; + +function walk( + root: any, + cb: (this: TraverseContext, v: any) => void, + options: InternalTraverseOptions = empty_null +) { + const path: PropertyKey[] = []; + const parents: any[] = []; + let alive = true; -function walk(root, cb) { - var path = []; - var parents = []; - var alive = true; - var options = arguments.length > 2 ? arguments[2] : emptyNull; - var iteratorFunction = options.includeSymbols ? ownEnumerableKeys : objectKeys; - var immutable = !!options.immutable; + const iterator_function = options.includeSymbols ? own_enumerable_keys : Object.keys; + const immutable = !!options.immutable; return (function walker(node_) { - var node = immutable ? copy(node_, options) : node_; - var modifiers = {}; + const node = immutable ? copy(node_, options) : node_; + const modifiers = {} as { + before?: (this: TraverseContext, value: any) => void; + after?: (this: TraverseContext, value: any) => void; + pre?: (this: TraverseContext, child: any, key: any) => void; + post?: (this: TraverseContext, child: any) => void; + stop?: () => void; + }; - var keepGoing = true; + let keep_going = true; - var state = { - node: node, - node_: node_, - path: [].concat(path), + const state = { + node, + node_, + path: ([] as any[]).concat(path) as string[], parent: parents[parents.length - 1], - parents: parents, + parents, key: path[path.length - 1], isRoot: path.length === 0, level: path.length, - circular: null, - update: function (x, stopHere) { + circular: undefined, + isLeaf: false as boolean, + notLeaf: true as boolean, + notRoot: true as boolean, + isFirst: false as boolean, + isLast: false as boolean, + update: function (x: any, stopHere: boolean = false) { if (!state.isRoot) { state.parent.node[state.key] = x; } state.node = x; if (stopHere) { - keepGoing = false; + keep_going = false; } }, - delete: function (stopHere) { + delete: function (stopHere: boolean) { delete state.parent.node[state.key]; if (stopHere) { - keepGoing = false; + keep_going = false; } }, - remove: function (stopHere) { - if (isArray(state.parent.node)) { + remove: function (stopHere: boolean) { + if (is_array(state.parent.node)) { state.parent.node.splice(state.key, 1); - console.log(787, state.parent.node); } else { delete state.parent.node[state.key]; } if (stopHere) { - keepGoing = false; + keep_going = false; } }, - keys: null, - before: function (f) { + keys: null as PropertyKey[] | null, + before: function (f: (this: TraverseContext, value: any) => void) { modifiers.before = f; }, - after: function (f) { + after: function (f: (this: TraverseContext, value: any) => void) { modifiers.after = f; }, - pre: function (f) { + pre: function (f: (this: TraverseContext, child: any, key: any) => void) { modifiers.pre = f; }, - post: function (f) { + post: function (f: (this: TraverseContext, child: any) => void) { modifiers.post = f; }, stop: function () { alive = false; }, block: function () { - keepGoing = false; + keep_going = false; }, - }; + } satisfies TraverseContext & { node_: any }; if (!alive) { return state; } - function updateState() { + function update_state() { if (typeof state.node === 'object' && state.node !== null) { if (!state.keys || state.node_ !== state.node) { - state.keys = iteratorFunction(state.node); + state.keys = iterator_function(state.node); } state.isLeaf = state.keys.length === 0; - for (var i = 0; i < parents.length; i++) { + for (let i = 0; i < parents.length; i++) { if (parents[i].node_ === node_) { state.circular = parents[i]; break; // eslint-disable-line no-restricted-syntax @@ -235,10 +340,10 @@ function walk(root, cb) { state.notRoot = !state.isRoot; } - updateState(); + update_state(); // use return values to update if defined - var ret = cb.call(state, state.node); + const ret = cb.call(state, state.node); if (ret !== undefined && state.update) { state.update(ret); } @@ -247,36 +352,36 @@ function walk(root, cb) { modifiers.before.call(state, state.node); } - if (!keepGoing) { + if (!keep_going) { return state; } if (typeof state.node === 'object' && state.node !== null && !state.circular) { parents.push(state); - updateState(); + update_state(); - forEach(state.keys, function (key, i) { + for (const [index, key] of Object.entries(state.keys ?? [])) { path.push(key); if (modifiers.pre) { modifiers.pre.call(state, state.node[key], key); } - var child = walker(state.node[key]); - if (immutable && hasOwnProperty.call(state.node, key) && !isWritable(state.node, key)) { + const child = walker(state.node[key]); + if (immutable && has_own_property.call(state.node, key) && !is_writable(state.node, key)) { state.node[key] = child.node; } - child.isLast = i === state.keys.length - 1; - child.isFirst = i === 0; + child.isLast = state.keys?.length ? +index === state.keys.length - 1 : false; + child.isFirst = +index === 0; if (modifiers.post) { modifiers.post.call(state, child); } path.pop(); - }); + } parents.pop(); } @@ -288,153 +393,181 @@ 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; -} +export class Traverse { + #value: any; + #options: TraverseOptions; -/** @type {(ps: PropertyKey[]) => Traverse['value']} */ -Traverse.prototype.get = function (ps) { - var node = this.value; - for (var i = 0; node && i < ps.length; i++) { - var key = ps[i]; - if ( - !hasOwnProperty.call(node, key) || - (!this.options.includeSymbols && typeof key === 'symbol') - ) { - return void undefined; - } - node = node[key]; + constructor(obj: any, options: TraverseOptions = empty_null) { + this.#value = obj; + this.#options = options; } - return node; -}; -/** @type {(ps: PropertyKey[]) => boolean} */ -Traverse.prototype.has = function (ps) { - var node = this.value; - for (var i = 0; node && i < ps.length; i++) { - var key = ps[i]; - if ( - !hasOwnProperty.call(node, key) || - (!this.options.includeSymbols && typeof key === 'symbol') - ) { - return false; + /** + * Get the element at the array `path`. + */ + get(paths: PropertyKey[]): any { + let node = this.#value; + + for (let i = 0; node && i < paths.length; i++) { + const key = paths[i]; + + if ( + !has_own_property.call(node, key) || + (!this.#options.includeSymbols && typeof key === 'symbol') + ) { + return void undefined; + } + + node = node[key]; } - node = node[key]; + + return node; } - return true; -}; -Traverse.prototype.set = function (ps, value) { - var node = this.value; - for (var i = 0; i < ps.length - 1; i++) { - var key = ps[i]; - if (!hasOwnProperty.call(node, key)) { - node[key] = {}; + /** + * Return whether the element at the array `path` exists. + */ + has(paths: string[]): boolean { + let node = this.#value; + + for (let i = 0; node && i < paths.length; i++) { + const key = paths[i]; + + if ( + !has_own_property.call(node, key) || + (!this.#options.includeSymbols && typeof key === 'symbol') + ) { + return false; + } + + node = node[key]; } - node = node[key]; + + return true; } - node[ps[i]] = value; - return value; -}; -Traverse.prototype.map = function (cb) { - return walk(this.value, cb, { - __proto__: null, - immutable: true, - includeSymbols: !!this.options.includeSymbols, - }); -}; + /** + * Set the element at the array `path` to `value`. + */ + set(path: string[], value: any): any { + let node = this.#value; -Traverse.prototype.forEach = function (cb) { - this.value = walk(this.value, cb, this.options); - return this.value; -}; + let i = 0; + for (i = 0; i < path.length - 1; i++) { + const key = path[i]; + + if (!has_own_property.call(node, key)) { + node[key] = {}; + } -Traverse.prototype.reduce = function (cb, init) { - var skip = arguments.length === 1; - var acc = skip ? this.value : init; - this.forEach(function (x) { - if (!this.isRoot || !skip) { - acc = cb.call(this, acc, x); + node = node[key]; } - }); - return acc; -}; -Traverse.prototype.paths = function () { - var acc = []; - this.forEach(function () { - acc.push(this.path); - }); - return acc; -}; + node[path[i]] = value; -Traverse.prototype.nodes = function () { - var acc = []; - this.forEach(function () { - acc.push(this.node); - }); - return acc; -}; + return value; + } -Traverse.prototype.clone = function () { - var parents = []; - var nodes = []; - var options = this.options; + /** + * Execute `fn` for each node in the object and return a new object with the results of the walk. To update nodes in the result use `this.update(value)`. + */ + map(cb: (this: TraverseContext, v: any) => void): any { + return walk(this.#value, cb, { + __proto__: null, + immutable: true, + includeSymbols: !!this.#options.includeSymbols, + }); + } - if (isTypedArray(this.value)) { - return taSlice(this.value); + /** + * Execute `fn` for each node in the object but unlike `.map()`, when `this.update()` is called it updates the object in-place. + */ + forEach(cb: (this: TraverseContext, v: any) => void): any { + this.#value = walk(this.#value, cb, this.#options as InternalTraverseOptions); + return this.#value; } - return (function clone(src) { - for (var i = 0; i < parents.length; i++) { - if (parents[i] === src) { - return nodes[i]; + /** + * For each node in the object, perform a [left-fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function)) with the return value of `fn(acc, node)`. + * + * If `init` isn't specified, `init` is set to the root object for the first step and the root element is skipped. + */ + reduce(cb: (this: TraverseContext, acc: any, v: any) => void, init?: any): any { + const skip = arguments.length === 1; + let acc = skip ? this.#value : init; + + this.forEach(function (x) { + if (!this.isRoot || !skip) { + acc = cb.call(this, acc, x); } - } + }); + + return acc; + } - if (typeof src === 'object' && src !== null) { - var dst = copy(src, options); + /** + * Return an `Array` of every possible non-cyclic path in the object. + * Paths are `Array`s of string keys. + */ + paths(): PropertyKey[][] { + const acc: PropertyKey[][] = []; - parents.push(src); - nodes.push(dst); + this.forEach(function () { + acc.push(this.path); + }); - var iteratorFunction = options.includeSymbols ? ownEnumerableKeys : objectKeys; - forEach(iteratorFunction(src), function (key) { - dst[key] = clone(src[key]); - }); + return acc; + } - parents.pop(); - nodes.pop(); - return dst; + /** + * Return an `Array` of every node in the object. + */ + nodes(): any[] { + const acc: any[] = []; + + this.forEach(function () { + acc.push(this.node); + }); + + return acc; + } + + /** + * Create a deep clone of the object. + */ + clone(): any { + const parents: any[] = []; + const nodes: any[] = []; + const options = this.#options; + + if (is_typed_array(this.#value)) { + return this.#value.slice(); } - return src; - })(this.value); -}; + return (function clone(src) { + for (let i = 0; i < parents.length; i++) { + if (parents[i] === src) { + return nodes[i]; + } + } -/** @type {(obj: object, options?: TraverseOptions) => Traverse} */ -function traverse(obj: object, options: TraverseOptions = emptyNull) { - return new Traverse(obj, options); -} + if (typeof src === 'object' && src !== null) { + const dst = copy(src, options); + + parents.push(src); + nodes.push(dst); -// TODO: replace with object.assign? -forEach(ownEnumerableKeys(Traverse.prototype), function (key) { - traverse[key] = function (obj) { - var args = [].slice.call(arguments, 1); - var t = new Traverse(obj); - return t[key].apply(t, args); - }; -}); + const iteratorFunction = options.includeSymbols ? own_enumerable_keys : Object.keys; + for (const key of iteratorFunction(src)) { + dst[key] = clone(src[key]); + } + + parents.pop(); + nodes.pop(); + return dst; + } -export { traverse }; + return src; + })(this.#value); + } +} diff --git a/test/circular.test.ts b/test/circular.test.ts index d6ae812..ca5e344 100644 --- a/test/circular.test.ts +++ b/test/circular.test.ts @@ -1,11 +1,11 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('circular', function (t) { const obj = { x: 3 }; // @ts-expect-error obj.y = obj; - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { if (this.path.join('') === 'y') { // t.equal(util.inspect(this.circular.node), util.inspect(obj)); expect(this.circular?.node).toEqual(obj); @@ -18,7 +18,7 @@ test('deepCirc', function (t) { // @ts-expect-error obj.y[2] = obj; - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { if (this.circular) { expect(this.circular?.path).toEqual([]); expect(this.path).toEqual(['y', '2']); @@ -34,7 +34,7 @@ test('doubleCirc', function (t) { obj.x.push(obj.y); const circs: any[] = []; - traverse(obj).forEach(function (x) { + new Traverse(obj).forEach(function (x) { if (this.circular) { circs.push({ circ: this.circular, self: this, node: x }); } @@ -56,7 +56,7 @@ test('circDubForEach', function (t) { // @ts-expect-error obj.x.push(obj.y); - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { if (this.circular) { this.update('...'); } @@ -72,7 +72,7 @@ test('circDubMap', function (t) { // @ts-expect-error obj.x.push(obj.y); - const c = traverse(obj).map(function () { + const c = new Traverse(obj).map(function () { if (this.circular) { this.update('...'); } @@ -88,7 +88,7 @@ test('circClone', function (t) { // @ts-expect-error obj.x.push(obj.y); - const clone = traverse(obj).clone(); + const clone = new Traverse(obj).clone(); expect(obj).not.toBe(clone); expect(clone.y[2]).toBe(clone); @@ -104,7 +104,7 @@ test('circMapScrub', function (t) { // @ts-expect-error obj.c = obj; - const scrubbed = traverse(obj).map(function () { + const scrubbed = new Traverse(obj).map(function () { if (this.circular) { this.remove(); } diff --git a/test/date.test.ts b/test/date.test.ts index b7fae1d..dcf840b 100644 --- a/test/date.test.ts +++ b/test/date.test.ts @@ -1,12 +1,12 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('dateEach', function (t) { var obj = { x: new Date(), y: 10, z: 5 }; var counts = {}; - traverse(obj).forEach(function (node) { + new Traverse(obj).forEach(function (node) { var type = (node instanceof Date && 'Date') || typeof node; counts[type] = (counts[type] || 0) + 1; }); @@ -21,12 +21,13 @@ test('dateEach', function (t) { test('dateMap', function (t) { var obj = { x: new Date(), y: 10, z: 5 }; - var res = traverse(obj).map(function (node) { + var res = new Traverse(obj).map(function (node) { if (typeof node === 'number') { this.update(node + 100); } }); + console.log(res.x); expect(obj.x).not.toBe(res.x); expect(res).toEqual({ x: obj.x, diff --git a/test/error.test.ts b/test/error.test.ts index 4ecf277..3cad952 100644 --- a/test/error.test.ts +++ b/test/error.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; -test('traverse an Error', () => { +test('new Traverse an Error', () => { var obj = new Error('test'); - var results = traverse(obj).map(function () {}); + var results = new Traverse(obj).map(function () {}); // t.same(results, { message: 'test' }); expect(results).toEqual({ message: 'test' }); }); diff --git a/test/has.test.ts b/test/has.test.ts index e224f92..fbae541 100644 --- a/test/has.test.ts +++ b/test/has.test.ts @@ -1,15 +1,15 @@ import { describe, expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; describe('has', function (t) { var obj = { a: 2, b: [4, 5, { c: 6 }] }; - expect(traverse(obj).has(['b', 2, 'c'])).toBe(true); - expect(traverse(obj).has(['b', 2, 'c', 0])).toBe(false); - expect(traverse(obj).has(['b', 2, 'd'])).toBe(false); - expect(traverse(obj).has([])).toBe(true); - expect(traverse(obj).has(['a'])).toBe(true); - expect(traverse(obj).has(['a', 2])).toBe(false); + expect(new Traverse(obj).has(['b', 2, 'c'])).toBe(true); + expect(new Traverse(obj).has(['b', 2, 'c', 0])).toBe(false); + expect(new Traverse(obj).has(['b', 2, 'd'])).toBe(false); + expect(new Traverse(obj).has([])).toBe(true); + expect(new Traverse(obj).has(['a'])).toBe(true); + expect(new Traverse(obj).has(['a', 2])).toBe(false); test('symbols', () => { /* eslint no-restricted-properties: 1 */ @@ -20,21 +20,23 @@ describe('has', function (t) { obj[globalSymbol][localSymbol] = 7; obj[localSymbol] = 8; - expect(traverse(obj).has([globalSymbol])).toBe(false); - expect(traverse(obj, { includeSymbols: true }).has([globalSymbol])).toBe(true); + expect(new Traverse(obj).has([globalSymbol])).toBe(false); + expect(new Traverse(obj, { includeSymbols: true }).has([globalSymbol])).toBe(true); - expect(traverse(obj).has([globalSymbol, globalSymbol])).toBe(false); - expect(traverse(obj, { includeSymbols: true }).has([globalSymbol, globalSymbol])).toBe(false); + expect(new Traverse(obj).has([globalSymbol, globalSymbol])).toBe(false); + expect(new Traverse(obj, { includeSymbols: true }).has([globalSymbol, globalSymbol])).toBe( + false + ); - expect(traverse(obj).has([globalSymbol, localSymbol])).toBe(false); - expect(traverse(obj, { includeSymbols: true }).has([globalSymbol, localSymbol])).toBe(true); + expect(new Traverse(obj).has([globalSymbol, localSymbol])).toBe(false); + expect(new Traverse(obj, { includeSymbols: true }).has([globalSymbol, localSymbol])).toBe(true); - expect(traverse(obj).has([localSymbol])).toBe(false); - expect(traverse(obj, { includeSymbols: true }).has([localSymbol])).toBe(true); + expect(new Traverse(obj).has([localSymbol])).toBe(false); + expect(new Traverse(obj, { includeSymbols: true }).has([localSymbol])).toBe(true); - expect(traverse(obj).has([Symbol('d')])).toBe(false); - expect(traverse(obj, { includeSymbols: true }).has([Symbol('d')])).toBe(false); + expect(new Traverse(obj).has([Symbol('d')])).toBe(false); + expect(new Traverse(obj, { includeSymbols: true }).has([Symbol('d')])).toBe(false); - expect(traverse(obj).has([Symbol('e')])).toBe(false); + expect(new Traverse(obj).has([Symbol('e')])).toBe(false); }); }); diff --git a/test/instance.test.ts b/test/instance.test.ts index fbb0f3d..fc41d81 100644 --- a/test/instance.test.ts +++ b/test/instance.test.ts @@ -1,11 +1,11 @@ import { test, expect } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; import { EventEmitter } from 'node:events'; test('check instanceof on node elems', function (t) { const counts = { emitter: 0 }; - traverse([new EventEmitter(), 3, 4, { ev: new EventEmitter() }]).forEach(function (node) { + new Traverse([new EventEmitter(), 3, 4, { ev: new EventEmitter() }]).forEach(function (node) { if (node instanceof EventEmitter) { counts.emitter += 1; } diff --git a/test/interface.test.ts b/test/interface.test.ts index 9d6a5ba..0b822ef 100644 --- a/test/interface.test.ts +++ b/test/interface.test.ts @@ -1,19 +1,19 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('interface map', function (t) { const obj = { a: [5, 6, 7], b: { c: [8] } }; expect( - traverse - .paths(obj) + new Traverse(obj) + .paths() .sort() .map((path) => path.join('/')) .slice(1) .join(' ') ).toBe('a a/0 a/1 a/2 b b/c b/c/0'); - expect(traverse.nodes(obj)).toEqual([ + expect(new Traverse(obj).nodes()).toEqual([ { a: [5, 6, 7], b: { c: [8] } }, [5, 6, 7], 5, @@ -25,7 +25,7 @@ test('interface map', function (t) { ]); // t.same( - // traverse.map(obj, function (node) { + // new Traverse.map(obj, function (node) { // if (typeof node === 'number') { // return node + 1000; // } @@ -37,7 +37,7 @@ test('interface map', function (t) { // { a: '5 6 7', b: { c: '8' } } // ); expect( - traverse.map(obj, (node) => { + new Traverse(obj).map((node) => { if (typeof node === 'number') { return node + 1000; } @@ -49,7 +49,7 @@ test('interface map', function (t) { ).toEqual({ a: '5 6 7', b: { c: '8' } }); var nodes = 0; - traverse.forEach(obj, function () { + new Traverse(obj).forEach(function () { nodes += 1; }); diff --git a/test/json.test.ts b/test/json.test.ts index 2c4fdba..b161bad 100644 --- a/test/json.test.ts +++ b/test/json.test.ts @@ -1,12 +1,12 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('json test', function (t) { var id = 54; var callbacks = {}; var obj = { moo: function () {}, foo: [2, 3, 4, function () {}] }; - var scrubbed = traverse(obj).map(function (x) { + var scrubbed = new Traverse(obj).map(function (x) { if (typeof x === 'function') { callbacks[id] = { id: id, f: x, path: this.path }; this.update('[Function]'); diff --git a/test/keys.test.ts b/test/keys.test.ts index f92a778..c05bd2f 100644 --- a/test/keys.test.ts +++ b/test/keys.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('sort test', function (t) { const acc: any[] = []; - traverse({ + new Traverse({ a: 30, b: 22, id: 9, diff --git a/test/leaves.test.ts b/test/leaves.test.ts index 0ec8ebc..c17c367 100644 --- a/test/leaves.test.ts +++ b/test/leaves.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('leaves test', function (t) { const acc: any[] = []; - traverse({ + new Traverse({ a: [1, 2, 3], b: 4, c: [5, 6], diff --git a/test/mutability.test.ts b/test/mutability.test.ts index 8cb5c59..a0f67f2 100644 --- a/test/mutability.test.ts +++ b/test/mutability.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert'; import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('mutate', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).forEach(function (x) { + const res = new Traverse(obj).forEach(function (x) { if (typeof x === 'number' && x % 2 === 0) { this.update(x * 10); } @@ -16,7 +16,7 @@ test('mutate', function (t) { test('map', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).map(function (x) { + const res = new Traverse(obj).map(function (x) { if (typeof x === 'number' && x % 2 === 0) { this.update(x * 10); } @@ -28,7 +28,7 @@ test('map', function (t) { test('clone', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).clone(); + const res = new Traverse(obj).clone(); expect(obj).toEqual(res); expect(obj).not.toBe(res); @@ -42,7 +42,7 @@ test('clone', function (t) { // TODO: Investigate why clone gives a different Uint8Array test('cloneTypedArray', function (t) { const obj = new Uint8Array([1]); - const res = traverse(obj).clone(); + const res = new Traverse(obj).clone(); console.log(23, obj, res); @@ -58,7 +58,7 @@ test('cloneTypedArray', function (t) { test('reduce', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).reduce(function (acc, x) { + const res = new Traverse(obj).reduce(function (acc, x) { if (this.isLeaf) { acc.push(x); } @@ -71,7 +71,7 @@ test('reduce', function (t) { test('reduceInit', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).reduce(function (acc) { + const res = new Traverse(obj).reduce(function (acc) { if (this.isRoot) { assert.fail('got root'); } @@ -85,7 +85,7 @@ test('reduceInit', function (t) { test('remove', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - traverse(obj).forEach(function (x) { + new Traverse(obj).forEach(function (x) { if (this.isLeaf && x % 2 === 0) { this.remove(); } @@ -98,7 +98,7 @@ test('removeNoStop', function (t) { const obj = { a: 1, b: 2, c: { d: 3, e: 4 }, f: 5 }; const keys = []; - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { keys.push(this.key); if (this.key === 'c') { this.remove(); @@ -112,7 +112,7 @@ test('removeStop', function (t) { const obj = { a: 1, b: 2, c: { d: 3, e: 4 }, f: 5 }; const keys = []; - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { keys.push(this.key); if (this.key === 'c') { this.remove(true); @@ -124,7 +124,7 @@ test('removeStop', function (t) { test('removeMap', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).map(function (x) { + const res = new Traverse(obj).map(function (x) { if (this.isLeaf && x % 2 === 0) { this.remove(); } @@ -136,7 +136,7 @@ test('removeMap', function (t) { test('delete', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - traverse(obj).forEach(function (x) { + new Traverse(obj).forEach(function (x) { if (this.isLeaf && x % 2 === 0) { this.delete(); } @@ -151,7 +151,7 @@ test('deleteNoStop', function (t) { const obj = { a: 1, b: 2, c: { d: 3, e: 4 } }; const keys = []; - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { keys.push(this.key); if (this.key === 'c') { this.delete(); @@ -165,7 +165,7 @@ test('deleteStop', function (t) { const obj = { a: 1, b: 2, c: { d: 3, e: 4 } }; const keys = []; - traverse(obj).forEach(function () { + new Traverse(obj).forEach(function () { keys.push(this.key); if (this.key === 'c') { this.delete(true); @@ -177,7 +177,7 @@ test('deleteStop', function (t) { test('deleteRedux', function (t) { const obj = { a: 1, b: 2, c: [3, 4, 5] }; - traverse(obj).forEach(function (x) { + new Traverse(obj).forEach(function (x) { if (this.isLeaf && x % 2 === 0) { this.delete(); } @@ -191,7 +191,7 @@ test('deleteRedux', function (t) { test('deleteMap', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).map(function (x) { + const res = new Traverse(obj).map(function (x) { if (this.isLeaf && x % 2 === 0) { this.delete(); } @@ -210,7 +210,7 @@ test('deleteMap', function (t) { test('deleteMapRedux', function (t) { const obj = { a: 1, b: 2, c: [3, 4, 5] }; - const res = traverse(obj).map(function (x) { + const res = new Traverse(obj).map(function (x) { if (this.isLeaf && x % 2 === 0) { this.delete(); } @@ -231,7 +231,7 @@ test('deleteMapRedux', function (t) { test('objectToString', function (t) { const obj = { a: 1, b: 2, c: [3, 4] }; - const res = traverse(obj).forEach(function (x) { + const res = new Traverse(obj).forEach(function (x) { if (typeof x === 'object' && !this.isRoot) { this.update(JSON.stringify(x)); } @@ -243,7 +243,7 @@ test('objectToString', function (t) { test('stringToObject', function (t) { const obj = { a: 1, b: 2, c: '[3,4]' }; - const res = traverse(obj).forEach(function (x) { + const res = new Traverse(obj).forEach(function (x) { if (typeof x === 'string') { this.update(JSON.parse(x)); } else if (typeof x === 'number' && x % 2 === 0) { diff --git a/test/negative.test.ts b/test/negative.test.ts index b3de485..8b9181f 100644 --- a/test/negative.test.ts +++ b/test/negative.test.ts @@ -1,9 +1,9 @@ import { describe, expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; describe('negative update test', function (t) { var obj = [5, 6, -3, [7, 8, -2, 1], { f: 10, g: -13 }]; - var fixed = traverse(obj).map(function (x) { + var fixed = new Traverse(obj).map(function (x) { if (x < 0) { this.update(x + 128); } diff --git a/test/obj.test.ts b/test/obj.test.ts index 853ca0d..748281e 100644 --- a/test/obj.test.ts +++ b/test/obj.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; -test('traverse an object with nested functions', function (t) { +test('new Traverse an object with nested functions', function (t) { function Cons(x) { expect(x).toBe(10); } - traverse(new Cons(10)); + new Traverse(new Cons(10)); }); diff --git a/test/siblings.test.ts b/test/siblings.test.ts index 1825c91..f567f86 100644 --- a/test/siblings.test.ts +++ b/test/siblings.test.ts @@ -1,10 +1,10 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('siblings', function (t) { var obj = { a: 1, b: 2, c: [4, 5, 6] }; - var res = traverse(obj).reduce(function (acc) { + var res = new Traverse(obj).reduce(function (acc) { /* eslint no-param-reassign: 0 */ var p = '/' + this.path.join('/'); if (this.parent) { diff --git a/test/stop.test.ts b/test/stop.test.ts index 3a3d731..d92db35 100644 --- a/test/stop.test.ts +++ b/test/stop.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('stop', function (t) { let visits = 0; - traverse('abcdefghij'.split('')).forEach(function (node) { + new Traverse('abcdefghij'.split('')).forEach(function (node) { if (typeof node === 'string') { visits += 1; if (node === 'e') { @@ -16,7 +16,7 @@ test('stop', function (t) { }); test('stopMap', function (t) { - var s = traverse('abcdefghij'.split('')) + var s = new Traverse('abcdefghij'.split('')) .map(function (node) { if (typeof node === 'string') { if (node === 'e') { @@ -36,7 +36,7 @@ test('stopReduce', function (t) { a: [4, 5], b: [6, [7, 8, 9]], }; - var xs = traverse(obj).reduce(function (acc, node) { + var xs = new Traverse(obj).reduce(function (acc, node) { if (this.isLeaf) { if (node === 7) { this.stop(); diff --git a/test/stringify.test.ts b/test/stringify.test.ts index 157361c..a34d16c 100644 --- a/test/stringify.test.ts +++ b/test/stringify.test.ts @@ -1,11 +1,11 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('stringify', function (t) { var obj = [5, 6, -3, [7, 8, -2, 1], { f: 10, g: -13 }]; var s = ''; - traverse(obj).forEach(function (node) { + new Traverse(obj).forEach(function (node) { if (Array.isArray(node)) { this.before(function () { s += '['; diff --git a/test/subexpr.test.ts b/test/subexpr.test.ts index f06db03..169b014 100644 --- a/test/subexpr.test.ts +++ b/test/subexpr.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; test('subexpr', function (t) { var obj = ['a', 4, 'b', 5, 'c', 6]; - var r = traverse(obj).map(function (x) { + var r = new Traverse(obj).map(function (x) { if (typeof x === 'number') { this.update([x - 0.1, x, x + 0.1], true); } @@ -15,7 +15,7 @@ test('subexpr', function (t) { test('block', { skip: true }, function (t) { var obj = [[1], [2], [3]]; - var r = traverse(obj).map(function (x) { + var r = new Traverse(obj).map(function (x) { if (Array.isArray(x) && !this.isRoot) { if (x[0] === 5) { this.block(); diff --git a/test/super_deep.test.ts b/test/super_deep.test.ts index 5ffa9e2..e4848f2 100644 --- a/test/super_deep.test.ts +++ b/test/super_deep.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; function make() { var a: any = { self: 'a' }; diff --git a/test/typed-array.test.ts b/test/typed-array.test.ts index 41ec04f..feec715 100644 --- a/test/typed-array.test.ts +++ b/test/typed-array.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; -import { traverse } from '../src'; +import { Traverse } from '../src'; -test('traverse an Uint8Array', { skip: typeof Uint8Array !== 'function' }, function (t) { +test('new Traverse an Uint8Array', { skip: typeof Uint8Array !== 'function' }, function (t) { var obj = new Uint8Array(4); - var results = traverse(obj).map(function () {}); + var results = new Traverse(obj).map(function () {}); console.log(obj, results);