VariableDeclarator.js 4.5 KB

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