no-deprecated-clone.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // @flow
  2. "use strict";
  3. function getVariableDefinition(name /*: string */, scope /*: Scope */) {
  4. let currentScope = scope;
  5. do {
  6. const variable = currentScope.set.get(name);
  7. if (variable && variable.defs[0]) {
  8. return { scope: currentScope, definition: variable.defs[0] };
  9. }
  10. } while ((currentScope = currentScope.upper));
  11. }
  12. /*::
  13. type ReferenceOriginImport = { kind: "import", source: string, name: string };
  14. type ReferenceOriginParam = {
  15. kind: "export param",
  16. exportName: string,
  17. index: number,
  18. };
  19. type ReferenceOrigin =
  20. | ReferenceOriginImport
  21. | ReferenceOriginParam
  22. | { kind: "import *", source: string }
  23. | {
  24. kind: "property",
  25. base: ReferenceOriginImport | ReferenceOriginParam,
  26. path: string,
  27. };
  28. */
  29. // Given a node and a context, returns a description of where its value comes
  30. // from.
  31. // It resolves imports, parameters of exported functions and property accesses.
  32. // See the ReferenceOrigin type for more informations.
  33. function getReferenceOrigin(
  34. node /*: Node */,
  35. scope /*: Scope */
  36. ) /*: ?ReferenceOrigin */ {
  37. if (node.type === "Identifier") {
  38. const variable = getVariableDefinition(node.name, scope);
  39. if (!variable) return null;
  40. const definition = variable.definition;
  41. const defNode = definition.node;
  42. if (definition.type === "ImportBinding") {
  43. if (defNode.type === "ImportSpecifier") {
  44. return {
  45. kind: "import",
  46. source: definition.parent.source.value,
  47. name: defNode.imported.name,
  48. };
  49. }
  50. if (defNode.type === "ImportNamespaceSpecifier") {
  51. return {
  52. kind: "import *",
  53. source: definition.parent.source.value,
  54. };
  55. }
  56. }
  57. if (definition.type === "Variable" && defNode.init) {
  58. const origin = getReferenceOrigin(defNode.init, variable.scope);
  59. return origin && patternToProperty(definition.name, origin);
  60. }
  61. if (definition.type === "Parameter") {
  62. const parent = defNode.parent;
  63. let exportName /*: string */;
  64. if (parent.type === "ExportDefaultDeclaration") {
  65. exportName = "default";
  66. } else if (parent.type === "ExportNamedDeclaration") {
  67. exportName = defNode.id.name;
  68. } else if (
  69. parent.type === "AssignmentExpression" &&
  70. parent.left.type === "MemberExpression" &&
  71. parent.left.object.type === "Identifier" &&
  72. parent.left.object.name === "module" &&
  73. parent.left.property.type === "Identifier" &&
  74. parent.left.property.name === "exports"
  75. ) {
  76. exportName = "module.exports";
  77. } else {
  78. return null;
  79. }
  80. return patternToProperty(definition.name, {
  81. kind: "export param",
  82. exportName,
  83. index: definition.index,
  84. });
  85. }
  86. }
  87. if (node.type === "MemberExpression" && !node.computed) {
  88. const origin = getReferenceOrigin(node.object, scope);
  89. return origin && addProperty(origin, node.property.name);
  90. }
  91. return null;
  92. }
  93. function patternToProperty(
  94. id /*: Node */,
  95. base /*: ReferenceOrigin */
  96. ) /*: ?ReferenceOrigin */ {
  97. const path = getPatternPath(id);
  98. return path && path.reduce(addProperty, base);
  99. }
  100. // Adds a property to a given origin. If it was a namespace import it becomes
  101. // a named import, so that `import * as x from "foo"; x.bar` and
  102. // `import { bar } from "foo"` have the same origin.
  103. function addProperty(
  104. origin /*: ReferenceOrigin */,
  105. name /*: string */
  106. ) /* ReferenceOrigin */ {
  107. if (origin.kind === "import *") {
  108. return {
  109. kind: "import",
  110. source: origin.source,
  111. name,
  112. };
  113. }
  114. if (origin.kind === "property") {
  115. return {
  116. kind: "property",
  117. base: origin.base,
  118. path: origin.path + "." + name,
  119. };
  120. }
  121. return {
  122. kind: "property",
  123. base: origin,
  124. path: name,
  125. };
  126. }
  127. // if "node" is c of { a: { b: c } }, the result is ["a","b"]
  128. function getPatternPath(node /*: Node */) /*: ?string[] */ {
  129. let current = node;
  130. const path = [];
  131. // Unshift keys to path while going up
  132. do {
  133. const property = current.parent;
  134. if (
  135. property.type === "ArrayPattern" ||
  136. property.type === "AssignmentPattern" ||
  137. property.computed
  138. ) {
  139. // These nodes are not supported.
  140. return null;
  141. }
  142. if (property.type === "Property") {
  143. path.unshift(property.key.name);
  144. } else {
  145. // The destructuring pattern is finished
  146. break;
  147. }
  148. } while ((current = current.parent.parent));
  149. return path;
  150. }
  151. function reportError(context /*: Context */, node /*: Node */) {
  152. const isMemberExpression = node.type === "MemberExpression";
  153. const id = isMemberExpression ? node.property : node;
  154. context.report({
  155. node: id,
  156. message: `t.${id.name}() is deprecated. Use t.cloneNode() instead.`,
  157. fix(fixer) {
  158. if (isMemberExpression) {
  159. return fixer.replaceText(id, "cloneNode");
  160. }
  161. },
  162. });
  163. }
  164. module.exports = {
  165. meta: {
  166. schema: [],
  167. fixable: "code",
  168. },
  169. create(context /*: Context */) {
  170. return {
  171. CallExpression(node /*: Node */) {
  172. const origin = getReferenceOrigin(node.callee, context.getScope());
  173. if (!origin) return;
  174. if (
  175. origin.kind === "import" &&
  176. (origin.name === "clone" || origin.name === "cloneDeep") &&
  177. origin.source === "@babel/types"
  178. ) {
  179. // imported from @babel/types
  180. return reportError(context, node.callee);
  181. }
  182. if (
  183. origin.kind === "property" &&
  184. (origin.path === "clone" || origin.path === "cloneDeep") &&
  185. origin.base.kind === "import" &&
  186. origin.base.name === "types" &&
  187. origin.base.source === "@babel/core"
  188. ) {
  189. // imported from @babel/core
  190. return reportError(context, node.callee);
  191. }
  192. if (
  193. origin.kind === "property" &&
  194. (origin.path === "types.clone" ||
  195. origin.path === "types.cloneDeep") &&
  196. origin.base.kind === "export param" &&
  197. (origin.base.exportName === "default" ||
  198. origin.base.exportName === "module.exports") &&
  199. origin.base.index === 0
  200. ) {
  201. // export default function ({ types: t }) {}
  202. // module.exports = function ({ types: t }) {}
  203. return reportError(context, node.callee);
  204. }
  205. },
  206. };
  207. },
  208. };
  209. /*:: // ESLint types
  210. type Node = { type: string, [string]: any };
  211. type Definition = {
  212. type: "ImportedBinding",
  213. name: Node,
  214. node: Node,
  215. parent: Node,
  216. };
  217. type Variable = {
  218. defs: Definition[],
  219. };
  220. type Scope = {
  221. set: Map<string, Variable>,
  222. upper: ?Scope,
  223. };
  224. type Context = {
  225. report(options: {
  226. node: Node,
  227. message: string,
  228. fix?: (fixer: Fixer) => ?Fixer,
  229. }): void,
  230. getScope(): Scope,
  231. };
  232. type Fixer = {
  233. replaceText(node: Node, replacement: string): Fixer,
  234. };
  235. */