Node.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. 'use strict';
  8. const Collection = require('../Collection');
  9. const matchNode = require('../matchNode');
  10. const once = require('../utils/once');
  11. const recast = require('recast');
  12. const Node = recast.types.namedTypes.Node;
  13. var types = recast.types.namedTypes;
  14. /**
  15. * @mixin
  16. */
  17. const traversalMethods = {
  18. /**
  19. * Find nodes of a specific type within the nodes of this collection.
  20. *
  21. * @param {type}
  22. * @param {filter}
  23. * @return {Collection}
  24. */
  25. find: function(type, filter) {
  26. const paths = [];
  27. const visitorMethodName = 'visit' + type;
  28. const visitor = {};
  29. function visit(path) {
  30. /*jshint validthis:true */
  31. if (!filter || matchNode(path.value, filter)) {
  32. paths.push(path);
  33. }
  34. this.traverse(path);
  35. }
  36. this.__paths.forEach(function(p, i) {
  37. const self = this;
  38. visitor[visitorMethodName] = function(path) {
  39. if (self.__paths[i] === path) {
  40. this.traverse(path);
  41. } else {
  42. return visit.call(this, path);
  43. }
  44. };
  45. recast.visit(p, visitor);
  46. }, this);
  47. return Collection.fromPaths(paths, this, type);
  48. },
  49. /**
  50. * Returns a collection containing the paths that create the scope of the
  51. * currently selected paths. Dedupes the paths.
  52. *
  53. * @return {Collection}
  54. */
  55. closestScope: function() {
  56. return this.map(path => path.scope && path.scope.path);
  57. },
  58. /**
  59. * Traverse the AST up and finds the closest node of the provided type.
  60. *
  61. * @param {Collection}
  62. * @param {filter}
  63. * @return {Collection}
  64. */
  65. closest: function(type, filter) {
  66. return this.map(function(path) {
  67. let parent = path.parent;
  68. while (
  69. parent &&
  70. !(
  71. type.check(parent.value) &&
  72. (!filter || matchNode(parent.value, filter))
  73. )
  74. ) {
  75. parent = parent.parent;
  76. }
  77. return parent || null;
  78. });
  79. },
  80. /**
  81. * Finds the declaration for each selected path. Useful for member expressions
  82. * or JSXElements. Expects a callback function that maps each path to the name
  83. * to look for.
  84. *
  85. * If the callback returns a falsey value, the element is skipped.
  86. *
  87. * @param {function} nameGetter
  88. *
  89. * @return {Collection}
  90. */
  91. getVariableDeclarators: function(nameGetter) {
  92. return this.map(function(path) {
  93. /*jshint curly:false*/
  94. let scope = path.scope;
  95. if (!scope) return;
  96. const name = nameGetter.apply(path, arguments);
  97. if (!name) return;
  98. scope = scope.lookup(name);
  99. if (!scope) return;
  100. const bindings = scope.getBindings()[name];
  101. if (!bindings) return;
  102. const decl = Collection.fromPaths(bindings)
  103. .closest(types.VariableDeclarator);
  104. if (decl.length === 1) {
  105. return decl.paths()[0];
  106. }
  107. }, types.VariableDeclarator);
  108. },
  109. };
  110. function toArray(value) {
  111. return Array.isArray(value) ? value : [value];
  112. }
  113. /**
  114. * @mixin
  115. */
  116. const mutationMethods = {
  117. /**
  118. * Simply replaces the selected nodes with the provided node. If a function
  119. * is provided it is executed for every node and the node is replaced with the
  120. * functions return value.
  121. *
  122. * @param {Node|Array<Node>|function} nodes
  123. * @return {Collection}
  124. */
  125. replaceWith: function(nodes) {
  126. return this.forEach(function(path, i) {
  127. const newNodes =
  128. (typeof nodes === 'function') ? nodes.call(path, path, i) : nodes;
  129. path.replace.apply(path, toArray(newNodes));
  130. });
  131. },
  132. /**
  133. * Inserts a new node before the current one.
  134. *
  135. * @param {Node|Array<Node>|function} insert
  136. * @return {Collection}
  137. */
  138. insertBefore: function(insert) {
  139. return this.forEach(function(path, i) {
  140. const newNodes =
  141. (typeof insert === 'function') ? insert.call(path, path, i) : insert;
  142. path.insertBefore.apply(path, toArray(newNodes));
  143. });
  144. },
  145. /**
  146. * Inserts a new node after the current one.
  147. *
  148. * @param {Node|Array<Node>|function} insert
  149. * @return {Collection}
  150. */
  151. insertAfter: function(insert) {
  152. return this.forEach(function(path, i) {
  153. const newNodes =
  154. (typeof insert === 'function') ? insert.call(path, path, i) : insert;
  155. path.insertAfter.apply(path, toArray(newNodes));
  156. });
  157. },
  158. remove: function() {
  159. return this.forEach(path => path.prune());
  160. }
  161. };
  162. function register() {
  163. Collection.registerMethods(traversalMethods, Node);
  164. Collection.registerMethods(mutationMethods, Node);
  165. Collection.setDefaultCollectionType(Node);
  166. }
  167. exports.register = once(register);