Node.js 4.7 KB

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