VariableDeclarator.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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 NodeCollection = require('./Node');
  10. const once = require('../utils/once');
  11. const recast = require('recast');
  12. const astNodesAreEquivalent = recast.types.astNodesAreEquivalent;
  13. const b = recast.types.builders;
  14. var types = recast.types.namedTypes;
  15. const VariableDeclarator = recast.types.namedTypes.VariableDeclarator;
  16. /**
  17. * @mixin
  18. */
  19. const globalMethods = {
  20. /**
  21. * Finds all variable declarators, optionally filtered by name.
  22. *
  23. * @param {string} name
  24. * @return {Collection}
  25. */
  26. findVariableDeclarators: function(name) {
  27. const filter = name ? {id: {name: name}} : null;
  28. return this.find(VariableDeclarator, filter);
  29. }
  30. };
  31. const filterMethods = {
  32. /**
  33. * Returns a function that returns true if the provided path is a variable
  34. * declarator and requires one of the specified module names.
  35. *
  36. * @param {string|Array} names A module name or an array of module names
  37. * @return {Function}
  38. */
  39. requiresModule: function(names) {
  40. if (names && !Array.isArray(names)) {
  41. names = [names];
  42. }
  43. const requireIdentifier = b.identifier('require');
  44. return function(path) {
  45. const node = path.value;
  46. if (!VariableDeclarator.check(node) ||
  47. !types.CallExpression.check(node.init) ||
  48. !astNodesAreEquivalent(node.init.callee, requireIdentifier)) {
  49. return false;
  50. }
  51. return !names ||
  52. names.some(
  53. n => astNodesAreEquivalent(node.init.arguments[0], b.literal(n))
  54. );
  55. };
  56. }
  57. };
  58. /**
  59. * @mixin
  60. */
  61. const transformMethods = {
  62. /**
  63. * Renames a variable and all its occurrences.
  64. *
  65. * @param {string} newName
  66. * @return {Collection}
  67. */
  68. renameTo: function(newName) {
  69. // TODO: Include JSXElements
  70. return this.forEach(function(path) {
  71. const node = path.value;
  72. const oldName = node.id.name;
  73. const rootScope = path.scope;
  74. const rootPath = rootScope.path;
  75. Collection.fromPaths([rootPath])
  76. .find(types.Identifier, {name: oldName})
  77. .filter(function(path) { // ignore non-variables
  78. const parent = path.parent.node;
  79. if (
  80. types.MemberExpression.check(parent) &&
  81. parent.property === path.node &&
  82. !parent.computed
  83. ) {
  84. // obj.oldName
  85. return false;
  86. }
  87. if (
  88. types.Property.check(parent) &&
  89. parent.key === path.node &&
  90. !parent.computed
  91. ) {
  92. // { oldName: 3 }
  93. return false;
  94. }
  95. if (
  96. types.MethodDefinition.check(parent) &&
  97. parent.key === path.node &&
  98. !parent.computed
  99. ) {
  100. // class A { oldName() {} }
  101. return false;
  102. }
  103. if (
  104. types.ClassProperty.check(parent) &&
  105. parent.key === path.node &&
  106. !parent.computed
  107. ) {
  108. // class A { oldName = 3 }
  109. return false;
  110. }
  111. if (
  112. types.JSXAttribute.check(parent) &&
  113. parent.name === path.node &&
  114. !parent.computed
  115. ) {
  116. // <Foo oldName={oldName} />
  117. return false;
  118. }
  119. return true;
  120. })
  121. .forEach(function(path) {
  122. let scope = path.scope;
  123. while (scope && scope !== rootScope) {
  124. if (scope.declares(oldName)) {
  125. return;
  126. }
  127. scope = scope.parent;
  128. }
  129. if (scope) { // identifier must refer to declared variable
  130. // It may look like we filtered out properties,
  131. // but the filter only ignored property "keys", not "value"s
  132. // In shorthand properties, "key" and "value" both have an
  133. // Identifier with the same structure.
  134. const parent = path.parent.node;
  135. if (
  136. types.Property.check(parent) &&
  137. parent.shorthand &&
  138. !parent.method
  139. ) {
  140. path.parent.get('shorthand').replace(false);
  141. }
  142. path.get('name').replace(newName);
  143. }
  144. });
  145. });
  146. }
  147. };
  148. function register() {
  149. NodeCollection.register();
  150. Collection.registerMethods(globalMethods);
  151. Collection.registerMethods(transformMethods, VariableDeclarator);
  152. }
  153. exports.register = once(register);
  154. exports.filters = filterMethods;