index.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. 'use strict';
  2. // TODO: use call-bind, is-date, is-regex, is-string, is-boolean-object, is-number-object
  3. function toS(obj) { return Object.prototype.toString.call(obj); }
  4. function isDate(obj) { return toS(obj) === '[object Date]'; }
  5. function isRegExp(obj) { return toS(obj) === '[object RegExp]'; }
  6. function isError(obj) { return toS(obj) === '[object Error]'; }
  7. function isBoolean(obj) { return toS(obj) === '[object Boolean]'; }
  8. function isNumber(obj) { return toS(obj) === '[object Number]'; }
  9. function isString(obj) { return toS(obj) === '[object String]'; }
  10. // TODO: use isarray
  11. var isArray = Array.isArray || function isArray(xs) {
  12. return Object.prototype.toString.call(xs) === '[object Array]';
  13. };
  14. // TODO: use for-each?
  15. function forEach(xs, fn) {
  16. if (xs.forEach) { return xs.forEach(fn); }
  17. for (var i = 0; i < xs.length; i++) {
  18. fn(xs[i], i, xs);
  19. }
  20. return void undefined;
  21. }
  22. // TODO: use object-keys
  23. var objectKeys = Object.keys || function keys(obj) {
  24. var res = [];
  25. for (var key in obj) { res.push(key); } // eslint-disable-line no-restricted-syntax
  26. return res;
  27. };
  28. // TODO: use object.hasown
  29. var hasOwnProperty = Object.prototype.hasOwnProperty || function (obj, key) {
  30. return key in obj;
  31. };
  32. function copy(src) {
  33. if (typeof src === 'object' && src !== null) {
  34. var dst;
  35. if (isArray(src)) {
  36. dst = [];
  37. } else if (isDate(src)) {
  38. dst = new Date(src.getTime ? src.getTime() : src);
  39. } else if (isRegExp(src)) {
  40. dst = new RegExp(src);
  41. } else if (isError(src)) {
  42. dst = { message: src.message };
  43. } else if (isBoolean(src) || isNumber(src) || isString(src)) {
  44. dst = Object(src);
  45. } else if (Object.create && Object.getPrototypeOf) {
  46. dst = Object.create(Object.getPrototypeOf(src));
  47. } else if (src.constructor === Object) {
  48. dst = {};
  49. } else {
  50. var proto = (src.constructor && src.constructor.prototype)
  51. || src.__proto__
  52. || {};
  53. var T = function T() {}; // eslint-disable-line func-style, func-name-matching
  54. T.prototype = proto;
  55. dst = new T();
  56. }
  57. forEach(objectKeys(src), function (key) {
  58. dst[key] = src[key];
  59. });
  60. return dst;
  61. }
  62. return src;
  63. }
  64. function walk(root, cb, immutable) {
  65. var path = [];
  66. var parents = [];
  67. var alive = true;
  68. return (function walker(node_) {
  69. var node = immutable ? copy(node_) : node_;
  70. var modifiers = {};
  71. var keepGoing = true;
  72. var state = {
  73. node: node,
  74. node_: node_,
  75. path: [].concat(path),
  76. parent: parents[parents.length - 1],
  77. parents: parents,
  78. key: path[path.length - 1],
  79. isRoot: path.length === 0,
  80. level: path.length,
  81. circular: null,
  82. update: function (x, stopHere) {
  83. if (!state.isRoot) {
  84. state.parent.node[state.key] = x;
  85. }
  86. state.node = x;
  87. if (stopHere) { keepGoing = false; }
  88. },
  89. delete: function (stopHere) {
  90. delete state.parent.node[state.key];
  91. if (stopHere) { keepGoing = false; }
  92. },
  93. remove: function (stopHere) {
  94. if (isArray(state.parent.node)) {
  95. state.parent.node.splice(state.key, 1);
  96. } else {
  97. delete state.parent.node[state.key];
  98. }
  99. if (stopHere) { keepGoing = false; }
  100. },
  101. keys: null,
  102. before: function (f) { modifiers.before = f; },
  103. after: function (f) { modifiers.after = f; },
  104. pre: function (f) { modifiers.pre = f; },
  105. post: function (f) { modifiers.post = f; },
  106. stop: function () { alive = false; },
  107. block: function () { keepGoing = false; },
  108. };
  109. if (!alive) { return state; }
  110. function updateState() {
  111. if (typeof state.node === 'object' && state.node !== null) {
  112. if (!state.keys || state.node_ !== state.node) {
  113. state.keys = objectKeys(state.node);
  114. }
  115. state.isLeaf = state.keys.length === 0;
  116. for (var i = 0; i < parents.length; i++) {
  117. if (parents[i].node_ === node_) {
  118. state.circular = parents[i];
  119. break; // eslint-disable-line no-restricted-syntax
  120. }
  121. }
  122. } else {
  123. state.isLeaf = true;
  124. state.keys = null;
  125. }
  126. state.notLeaf = !state.isLeaf;
  127. state.notRoot = !state.isRoot;
  128. }
  129. updateState();
  130. // use return values to update if defined
  131. var ret = cb.call(state, state.node);
  132. if (ret !== undefined && state.update) { state.update(ret); }
  133. if (modifiers.before) { modifiers.before.call(state, state.node); }
  134. if (!keepGoing) { return state; }
  135. if (
  136. typeof state.node === 'object'
  137. && state.node !== null
  138. && !state.circular
  139. ) {
  140. parents.push(state);
  141. updateState();
  142. forEach(state.keys, function (key, i) {
  143. path.push(key);
  144. if (modifiers.pre) { modifiers.pre.call(state, state.node[key], key); }
  145. var child = walker(state.node[key]);
  146. if (immutable && hasOwnProperty.call(state.node, key)) {
  147. state.node[key] = child.node;
  148. }
  149. child.isLast = i === state.keys.length - 1;
  150. child.isFirst = i === 0;
  151. if (modifiers.post) { modifiers.post.call(state, child); }
  152. path.pop();
  153. });
  154. parents.pop();
  155. }
  156. if (modifiers.after) { modifiers.after.call(state, state.node); }
  157. return state;
  158. }(root)).node;
  159. }
  160. function Traverse(obj) {
  161. this.value = obj;
  162. }
  163. Traverse.prototype.get = function (ps) {
  164. var node = this.value;
  165. for (var i = 0; i < ps.length; i++) {
  166. var key = ps[i];
  167. if (!node || !hasOwnProperty.call(node, key)) {
  168. return void undefined;
  169. }
  170. node = node[key];
  171. }
  172. return node;
  173. };
  174. Traverse.prototype.has = function (ps) {
  175. var node = this.value;
  176. for (var i = 0; i < ps.length; i++) {
  177. var key = ps[i];
  178. if (!node || !hasOwnProperty.call(node, key)) {
  179. return false;
  180. }
  181. node = node[key];
  182. }
  183. return true;
  184. };
  185. Traverse.prototype.set = function (ps, value) {
  186. var node = this.value;
  187. for (var i = 0; i < ps.length - 1; i++) {
  188. var key = ps[i];
  189. if (!hasOwnProperty.call(node, key)) { node[key] = {}; }
  190. node = node[key];
  191. }
  192. node[ps[i]] = value;
  193. return value;
  194. };
  195. Traverse.prototype.map = function (cb) {
  196. return walk(this.value, cb, true);
  197. };
  198. Traverse.prototype.forEach = function (cb) {
  199. this.value = walk(this.value, cb, false);
  200. return this.value;
  201. };
  202. Traverse.prototype.reduce = function (cb, init) {
  203. var skip = arguments.length === 1;
  204. var acc = skip ? this.value : init;
  205. this.forEach(function (x) {
  206. if (!this.isRoot || !skip) {
  207. acc = cb.call(this, acc, x);
  208. }
  209. });
  210. return acc;
  211. };
  212. Traverse.prototype.paths = function () {
  213. var acc = [];
  214. this.forEach(function () {
  215. acc.push(this.path);
  216. });
  217. return acc;
  218. };
  219. Traverse.prototype.nodes = function () {
  220. var acc = [];
  221. this.forEach(function () {
  222. acc.push(this.node);
  223. });
  224. return acc;
  225. };
  226. Traverse.prototype.clone = function () {
  227. var parents = [];
  228. var nodes = [];
  229. return (function clone(src) {
  230. for (var i = 0; i < parents.length; i++) {
  231. if (parents[i] === src) {
  232. return nodes[i];
  233. }
  234. }
  235. if (typeof src === 'object' && src !== null) {
  236. var dst = copy(src);
  237. parents.push(src);
  238. nodes.push(dst);
  239. forEach(objectKeys(src), function (key) {
  240. dst[key] = clone(src[key]);
  241. });
  242. parents.pop();
  243. nodes.pop();
  244. return dst;
  245. }
  246. return src;
  247. }(this.value));
  248. };
  249. function traverse(obj) {
  250. return new Traverse(obj);
  251. }
  252. // TODO: replace with object.assign?
  253. forEach(objectKeys(Traverse.prototype), function (key) {
  254. traverse[key] = function (obj) {
  255. var args = [].slice.call(arguments, 1);
  256. var t = new Traverse(obj);
  257. return t[key].apply(t, args);
  258. };
  259. });
  260. module.exports = traverse;