Collection.js.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: Collection.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: Collection.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>/*
  20. * Copyright (c) 2015-present, Facebook, Inc.
  21. * All rights reserved.
  22. *
  23. * This source code is licensed under the BSD-style license found in the
  24. * LICENSE file in the root directory of this source tree. An additional grant
  25. * of patent rights can be found in the PATENTS file in the same directory.
  26. *
  27. */
  28. 'use strict';
  29. var assert = require('assert');
  30. var recast = require('recast');
  31. var _ = require('lodash');
  32. var astTypes = recast.types;
  33. var types = astTypes.namedTypes;
  34. var NodePath = astTypes.NodePath;
  35. var Node = types.Node;
  36. /**
  37. * This represents a generic collection of node paths. It only has a generic
  38. * API to access and process the elements of the list. It doesn't know anything
  39. * about AST types.
  40. *
  41. * @mixes traversalMethods
  42. * @mixes mutationMethods
  43. * @mixes transformMethods
  44. * @mixes globalMethods
  45. */
  46. class Collection {
  47. /**
  48. * @param {Array} paths An array of AST paths
  49. * @param {Collection} parent A parent collection
  50. * @param {Array} types An array of types all the paths in the collection
  51. * have in common. If not passed, it will be inferred from the paths.
  52. * @return {Collection}
  53. */
  54. constructor(paths, parent, types) {
  55. assert.ok(Array.isArray(paths), 'Collection is passed an array');
  56. assert.ok(
  57. paths.every(p => p instanceof NodePath),
  58. 'Array contains only paths'
  59. );
  60. this._parent = parent;
  61. this.__paths = paths;
  62. if (types &amp;&amp; !Array.isArray(types)) {
  63. types = _toTypeArray(types);
  64. } else if (!types || Array.isArray(types) &amp;&amp; types.length === 0) {
  65. types = _inferTypes(paths);
  66. }
  67. this._types = types.length === 0 ? _defaultType : types;
  68. }
  69. /**
  70. * Returns a new collection containing the nodes for which the callback
  71. * returns true.
  72. *
  73. * @param {function} callback
  74. * @return {Collection}
  75. */
  76. filter(callback) {
  77. return new this.constructor(this.__paths.filter(callback), this);
  78. }
  79. /**
  80. * Executes callback for each node/path in the collection.
  81. *
  82. * @param {function} callback
  83. * @return {Collection} The collection itself
  84. */
  85. forEach(callback) {
  86. this.__paths.forEach(
  87. (path, i, paths) => callback.call(path, path, i, paths)
  88. );
  89. return this;
  90. }
  91. /**
  92. * Executes the callback for every path in the collection and returns a new
  93. * collection from the return values (which must be paths).
  94. *
  95. * The callback can return null to indicate to exclude the element from the
  96. * new collection.
  97. *
  98. * If an array is returned, the array will be flattened into the result
  99. * collection.
  100. *
  101. * @param {function} callback
  102. * @param {Type} type Force the new collection to be of a specific type
  103. */
  104. map(callback, type) {
  105. var paths = [];
  106. this.forEach(function(path) {
  107. /*jshint eqnull:true*/
  108. var result = callback.apply(path, arguments);
  109. if (result == null) return;
  110. if (!Array.isArray(result)) {
  111. result = [result];
  112. }
  113. for (var i = 0; i &lt; result.length; i++) {
  114. if (paths.indexOf(result[i]) === -1) {
  115. paths.push(result[i]);
  116. }
  117. }
  118. });
  119. return fromPaths(paths, this, type);
  120. }
  121. /**
  122. * Returns the number of elements in this collection.
  123. *
  124. * @return {number}
  125. */
  126. size() {
  127. return this.__paths.length;
  128. }
  129. /**
  130. * Returns the number of elements in this collection.
  131. *
  132. * @return {number}
  133. */
  134. get length() {
  135. return this.__paths.length;
  136. }
  137. /**
  138. * Returns an array of AST nodes in this collection.
  139. *
  140. * @return {Array}
  141. */
  142. nodes() {
  143. return this.__paths.map(p => p.value);
  144. }
  145. paths() {
  146. return this.__paths;
  147. }
  148. getAST() {
  149. if (this._parent) {
  150. return this._parent.getAST();
  151. }
  152. return this.__paths;
  153. }
  154. toSource(options) {
  155. if (this._parent) {
  156. return this._parent.toSource(options);
  157. }
  158. if (this.__paths.length === 1) {
  159. return recast.print(this.__paths[0], options).code;
  160. } else {
  161. return this.__paths.map(p => recast.print(p, options).code);
  162. }
  163. }
  164. /**
  165. * Returns a new collection containing only the element at position index.
  166. *
  167. * In case of a negative index, the element is taken from the end:
  168. *
  169. * .at(0) - first element
  170. * .at(-1) - last element
  171. *
  172. * @param {number} index
  173. * @return {Collection}
  174. */
  175. at(index) {
  176. return fromPaths(
  177. this.__paths.slice(
  178. index,
  179. index === -1 ? undefined : index + 1
  180. ),
  181. this
  182. );
  183. }
  184. /**
  185. * Proxies to NodePath#get of the first path.
  186. *
  187. * @param {string|number} ...fields
  188. */
  189. get() {
  190. var path = this.__paths[0];
  191. if (!path) {
  192. throw Error(
  193. 'You cannot call "get" on a collection with no paths. ' +
  194. 'Instead, check the "length" property first to verify at least 1 path exists.'
  195. );
  196. }
  197. return path.get.apply(path, arguments);
  198. }
  199. /**
  200. * Returns the type(s) of the collection. This is only used for unit tests,
  201. * I don't think other consumers would need it.
  202. *
  203. * @return {Array&lt;string>}
  204. */
  205. getTypes() {
  206. return this._types;
  207. }
  208. /**
  209. * Returns true if this collection has the type 'type'.
  210. *
  211. * @param {Type} type
  212. * @return {boolean}
  213. */
  214. isOfType(type) {
  215. return !!type &amp;&amp; this._types.indexOf(type.toString()) > -1;
  216. }
  217. }
  218. /**
  219. * Given a set of paths, this infers the common types of all paths.
  220. * @private
  221. * @param {Array} paths An array of paths.
  222. * @return {Type} type An AST type
  223. */
  224. function _inferTypes(paths) {
  225. var _types = [];
  226. if (paths.length > 0 &amp;&amp; Node.check(paths[0].node)) {
  227. var nodeType = types[paths[0].node.type];
  228. var sameType = paths.length === 1 ||
  229. paths.every(path => nodeType.check(path.node));
  230. if (sameType) {
  231. _types = [nodeType.toString()].concat(
  232. astTypes.getSupertypeNames(nodeType.toString())
  233. );
  234. } else {
  235. // try to find a common type
  236. _types = _.intersection.apply(
  237. null,
  238. paths.map(path => astTypes.getSupertypeNames(path.node.type))
  239. );
  240. }
  241. }
  242. return _types;
  243. }
  244. function _toTypeArray(value) {
  245. value = !Array.isArray(value) ? [value] : value;
  246. value = value.map(v => v.toString());
  247. if (value.length > 1) {
  248. return _.union(value, _.intersection.apply(
  249. null,
  250. value.map(type => astTypes.getSupertypeNames(type))
  251. ));
  252. } else {
  253. return value.concat(astTypes.getSupertypeNames(value[0]));
  254. }
  255. }
  256. /**
  257. * Creates a new collection from an array of node paths.
  258. *
  259. * If type is passed, it will create a typed collection if such a collection
  260. * exists. The nodes or path values must be of the same type.
  261. *
  262. * Otherwise it will try to infer the type from the path list. If every
  263. * element has the same type, a typed collection is created (if it exists),
  264. * otherwise, a generic collection will be created.
  265. *
  266. * @ignore
  267. * @param {Array} paths An array of paths
  268. * @param {Collection} parent A parent collection
  269. * @param {Type} type An AST type
  270. * @return {Collection}
  271. */
  272. function fromPaths(paths, parent, type) {
  273. assert.ok(
  274. paths.every(n => n instanceof NodePath),
  275. 'Every element in the array should be a NodePath'
  276. );
  277. return new Collection(paths, parent, type);
  278. }
  279. /**
  280. * Creates a new collection from an array of nodes. This is a convenience
  281. * method which converts the nodes to node paths first and calls
  282. *
  283. * Collections.fromPaths(paths, parent, type)
  284. *
  285. * @ignore
  286. * @param {Array} nodes An array of AST nodes
  287. * @param {Collection} parent A parent collection
  288. * @param {Type} type An AST type
  289. * @return {Collection}
  290. */
  291. function fromNodes(nodes, parent, type) {
  292. assert.ok(
  293. nodes.every(n => Node.check(n)),
  294. 'Every element in the array should be a Node'
  295. );
  296. return fromPaths(
  297. nodes.map(n => new NodePath(n)),
  298. parent,
  299. type
  300. );
  301. }
  302. var CPt = Collection.prototype;
  303. /**
  304. * This function adds the provided methods to the prototype of the corresponding
  305. * typed collection. If no type is passed, the methods are added to
  306. * Collection.prototype and are available for all collections.
  307. *
  308. * @param {Object} methods Methods to add to the prototype
  309. * @param {Type=} type Optional type to add the methods to
  310. */
  311. function registerMethods(methods, type) {
  312. for (var methodName in methods) {
  313. if (!methods.hasOwnProperty(methodName)) {
  314. return;
  315. }
  316. if (hasConflictingRegistration(methodName, type)) {
  317. var msg = `There is a conflicting registration for method with name "${methodName}".\nYou tried to register an additional method with `;
  318. if (type) {
  319. msg += `type "${type.toString()}".`
  320. } else {
  321. msg += 'universal type.'
  322. }
  323. msg += '\nThere are existing registrations for that method with ';
  324. var conflictingRegistrations = CPt[methodName].typedRegistrations;
  325. if (conflictingRegistrations) {
  326. msg += `type ${Object.keys(conflictingRegistrations).join(', ')}.`;
  327. } else {
  328. msg += 'universal type.';
  329. }
  330. throw Error(msg);
  331. }
  332. if (!type) {
  333. CPt[methodName] = methods[methodName];
  334. } else {
  335. type = type.toString();
  336. if (!CPt.hasOwnProperty(methodName)) {
  337. installTypedMethod(methodName);
  338. }
  339. var registrations = CPt[methodName].typedRegistrations;
  340. registrations[type] = methods[methodName];
  341. astTypes.getSupertypeNames(type).forEach(function (name) {
  342. registrations[name] = false;
  343. });
  344. }
  345. }
  346. }
  347. function installTypedMethod(methodName) {
  348. if (CPt.hasOwnProperty(methodName)) {
  349. throw new Error(`Internal Error: "${methodName}" method is already installed`);
  350. }
  351. var registrations = {};
  352. function typedMethod() {
  353. var types = Object.keys(registrations);
  354. for (var i = 0; i &lt; types.length; i++) {
  355. var currentType = types[i];
  356. if (registrations[currentType] &amp;&amp; this.isOfType(currentType)) {
  357. return registrations[currentType].apply(this, arguments);
  358. }
  359. }
  360. throw Error(
  361. `You have a collection of type [${this.getTypes()}]. ` +
  362. `"${methodName}" is only defined for one of [${types.join('|')}].`
  363. );
  364. }
  365. typedMethod.typedRegistrations = registrations;
  366. CPt[methodName] = typedMethod;
  367. }
  368. function hasConflictingRegistration(methodName, type) {
  369. if (!type) {
  370. return CPt.hasOwnProperty(methodName);
  371. }
  372. if (!CPt.hasOwnProperty(methodName)) {
  373. return false;
  374. }
  375. var registrations = CPt[methodName] &amp;&amp; CPt[methodName].typedRegistrations;
  376. if (!registrations) {
  377. return true;
  378. }
  379. type = type.toString();
  380. if (registrations.hasOwnProperty(type)) {
  381. return true;
  382. }
  383. return astTypes.getSupertypeNames(type.toString()).some(function (name) {
  384. return !!registrations[name];
  385. });
  386. }
  387. var _defaultType = [];
  388. /**
  389. * Sets the default collection type. In case a collection is created form an
  390. * empty set of paths and no type is specified, we return a collection of this
  391. * type.
  392. *
  393. * @ignore
  394. * @param {Type} type
  395. */
  396. function setDefaultCollectionType(type) {
  397. _defaultType = _toTypeArray(type);
  398. }
  399. exports.fromPaths = fromPaths;
  400. exports.fromNodes = fromNodes;
  401. exports.registerMethods = registerMethods;
  402. exports.hasConflictingRegistration = hasConflictingRegistration;
  403. exports.setDefaultCollectionType = setDefaultCollectionType;
  404. </code></pre>
  405. </article>
  406. </section>
  407. </div>
  408. <nav>
  409. <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-jscodeshift.html">jscodeshift</a></li></ul><h3>Externals</h3><ul><li><a href="external-astTypes.html">astTypes</a></li></ul><h3>Classes</h3><ul><li><a href="Collection.html">Collection</a></li></ul><h3>Mixins</h3><ul><li><a href="globalMethods.html">globalMethods</a></li><li><a href="mutationMethods.html">mutationMethods</a></li><li><a href="transformMethods.html">transformMethods</a></li><li><a href="traversalMethods.html">traversalMethods</a></li></ul><h3>Global</h3><ul><li><a href="global.html#registerMethods">registerMethods</a></li></ul>
  410. </nav>
  411. <br class="clear">
  412. <footer>
  413. Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.1</a> on Wed Sep 21 2016 16:53:09 GMT-0400 (EDT)
  414. </footer>
  415. <script> prettyPrint(); </script>
  416. <script src="scripts/linenumber.js"> </script>
  417. </body>
  418. </html>