no-deprecated.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /**
  2. * @fileoverview Prevent usage of deprecated methods
  3. * @author Yannick Croissant
  4. * @author Scott Feeney
  5. * @author Sergei Startsev
  6. */
  7. 'use strict';
  8. const entries = require('object.entries');
  9. const astUtil = require('../util/ast');
  10. const componentUtil = require('../util/componentUtil');
  11. const docsUrl = require('../util/docsUrl');
  12. const pragmaUtil = require('../util/pragma');
  13. const testReactVersion = require('../util/version').testReactVersion;
  14. const report = require('../util/report');
  15. const getText = require('../util/eslint').getText;
  16. // ------------------------------------------------------------------------------
  17. // Constants
  18. // ------------------------------------------------------------------------------
  19. const MODULES = {
  20. react: ['React'],
  21. 'react-addons-perf': ['ReactPerf', 'Perf'],
  22. 'react-dom': ['ReactDOM'],
  23. 'react-dom/server': ['ReactDOMServer'],
  24. };
  25. // ------------------------------------------------------------------------------
  26. // Rule Definition
  27. // ------------------------------------------------------------------------------
  28. function getDeprecated(pragma) {
  29. const deprecated = {};
  30. // 0.12.0
  31. deprecated[`${pragma}.renderComponent`] = ['0.12.0', `${pragma}.render`];
  32. deprecated[`${pragma}.renderComponentToString`] = ['0.12.0', `${pragma}.renderToString`];
  33. deprecated[`${pragma}.renderComponentToStaticMarkup`] = ['0.12.0', `${pragma}.renderToStaticMarkup`];
  34. deprecated[`${pragma}.isValidComponent`] = ['0.12.0', `${pragma}.isValidElement`];
  35. deprecated[`${pragma}.PropTypes.component`] = ['0.12.0', `${pragma}.PropTypes.element`];
  36. deprecated[`${pragma}.PropTypes.renderable`] = ['0.12.0', `${pragma}.PropTypes.node`];
  37. deprecated[`${pragma}.isValidClass`] = ['0.12.0'];
  38. deprecated['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})'];
  39. // 0.13.0
  40. deprecated[`${pragma}.addons.classSet`] = ['0.13.0', 'the npm module classnames'];
  41. deprecated[`${pragma}.addons.cloneWithProps`] = ['0.13.0', `${pragma}.cloneElement`];
  42. // 0.14.0
  43. deprecated[`${pragma}.render`] = ['0.14.0', 'ReactDOM.render'];
  44. deprecated[`${pragma}.unmountComponentAtNode`] = ['0.14.0', 'ReactDOM.unmountComponentAtNode'];
  45. deprecated[`${pragma}.findDOMNode`] = ['0.14.0', 'ReactDOM.findDOMNode'];
  46. deprecated[`${pragma}.renderToString`] = ['0.14.0', 'ReactDOMServer.renderToString'];
  47. deprecated[`${pragma}.renderToStaticMarkup`] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup'];
  48. // 15.0.0
  49. deprecated[`${pragma}.addons.LinkedStateMixin`] = ['15.0.0'];
  50. deprecated['ReactPerf.printDOM'] = ['15.0.0', 'ReactPerf.printOperations'];
  51. deprecated['Perf.printDOM'] = ['15.0.0', 'Perf.printOperations'];
  52. deprecated['ReactPerf.getMeasurementsSummaryMap'] = ['15.0.0', 'ReactPerf.getWasted'];
  53. deprecated['Perf.getMeasurementsSummaryMap'] = ['15.0.0', 'Perf.getWasted'];
  54. // 15.5.0
  55. deprecated[`${pragma}.createClass`] = ['15.5.0', 'the npm module create-react-class'];
  56. deprecated[`${pragma}.addons.TestUtils`] = ['15.5.0', 'ReactDOM.TestUtils'];
  57. deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types'];
  58. // 15.6.0
  59. deprecated[`${pragma}.DOM`] = ['15.6.0', 'the npm module react-dom-factories'];
  60. // 16.9.0
  61. // For now the following life-cycle methods are just legacy, not deprecated:
  62. // `componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate`
  63. // https://github.com/yannickcr/eslint-plugin-react/pull/1750#issuecomment-425975934
  64. deprecated.componentWillMount = [
  65. '16.9.0',
  66. 'UNSAFE_componentWillMount',
  67. 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount. '
  68. + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
  69. ];
  70. deprecated.componentWillReceiveProps = [
  71. '16.9.0',
  72. 'UNSAFE_componentWillReceiveProps',
  73. 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops. '
  74. + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
  75. ];
  76. deprecated.componentWillUpdate = [
  77. '16.9.0',
  78. 'UNSAFE_componentWillUpdate',
  79. 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate. '
  80. + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
  81. ];
  82. // 18.0.0
  83. // https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations
  84. deprecated['ReactDOM.render'] = [
  85. '18.0.0',
  86. 'createRoot',
  87. 'https://reactjs.org/link/switch-to-createroot',
  88. ];
  89. deprecated['ReactDOM.hydrate'] = [
  90. '18.0.0',
  91. 'hydrateRoot',
  92. 'https://reactjs.org/link/switch-to-createroot',
  93. ];
  94. deprecated['ReactDOM.unmountComponentAtNode'] = [
  95. '18.0.0',
  96. 'root.unmount',
  97. 'https://reactjs.org/link/switch-to-createroot',
  98. ];
  99. deprecated['ReactDOMServer.renderToNodeStream'] = [
  100. '18.0.0',
  101. 'renderToPipeableStream',
  102. 'https://reactjs.org/docs/react-dom-server.html#rendertonodestream',
  103. ];
  104. return deprecated;
  105. }
  106. const messages = {
  107. deprecated: '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}{{refs}}',
  108. };
  109. /** @type {import('eslint').Rule.RuleModule} */
  110. module.exports = {
  111. meta: {
  112. docs: {
  113. description: 'Disallow usage of deprecated methods',
  114. category: 'Best Practices',
  115. recommended: true,
  116. url: docsUrl('no-deprecated'),
  117. },
  118. messages,
  119. schema: [],
  120. },
  121. create(context) {
  122. const pragma = pragmaUtil.getFromContext(context);
  123. const deprecated = getDeprecated(pragma);
  124. function isDeprecated(method) {
  125. return (
  126. deprecated
  127. && deprecated[method]
  128. && deprecated[method][0]
  129. && testReactVersion(context, `>= ${deprecated[method][0]}`)
  130. );
  131. }
  132. function checkDeprecation(node, methodName, methodNode) {
  133. if (!isDeprecated(methodName)) {
  134. return;
  135. }
  136. const version = deprecated[methodName][0];
  137. const newMethod = deprecated[methodName][1];
  138. const refs = deprecated[methodName][2];
  139. report(context, messages.deprecated, 'deprecated', {
  140. node: methodNode || node,
  141. data: {
  142. oldMethod: methodName,
  143. version,
  144. newMethod: newMethod ? `, use ${newMethod} instead` : '',
  145. refs: refs ? `, see ${refs}` : '',
  146. },
  147. });
  148. }
  149. function getReactModuleName(node) {
  150. let moduleName = false;
  151. if (!node.init) {
  152. return false;
  153. }
  154. entries(MODULES).some((entry) => {
  155. const key = entry[0];
  156. const moduleNames = entry[1];
  157. if (
  158. node.init.arguments
  159. && node.init.arguments.length > 0
  160. && node.init.arguments[0]
  161. && key === node.init.arguments[0].value
  162. ) {
  163. moduleName = MODULES[key][0];
  164. } else {
  165. moduleName = moduleNames.find((name) => name === node.init.name);
  166. }
  167. return moduleName;
  168. });
  169. return moduleName;
  170. }
  171. /**
  172. * Returns life cycle methods if available
  173. * @param {ASTNode} node The AST node being checked.
  174. * @returns {Array} The array of methods.
  175. */
  176. function getLifeCycleMethods(node) {
  177. const properties = astUtil.getComponentProperties(node);
  178. return properties.map((property) => ({
  179. name: astUtil.getPropertyName(property),
  180. node: astUtil.getPropertyNameNode(property),
  181. }));
  182. }
  183. /**
  184. * Checks life cycle methods
  185. * @param {ASTNode} node The AST node being checked.
  186. */
  187. function checkLifeCycleMethods(node) {
  188. if (
  189. componentUtil.isES5Component(node, context)
  190. || componentUtil.isES6Component(node, context)
  191. ) {
  192. const methods = getLifeCycleMethods(node);
  193. methods.forEach((method) => checkDeprecation(node, method.name, method.node));
  194. }
  195. }
  196. // --------------------------------------------------------------------------
  197. // Public
  198. // --------------------------------------------------------------------------
  199. return {
  200. MemberExpression(node) {
  201. checkDeprecation(node, getText(context, node));
  202. },
  203. ImportDeclaration(node) {
  204. const isReactImport = typeof MODULES[node.source.value] !== 'undefined';
  205. if (!isReactImport) {
  206. return;
  207. }
  208. node.specifiers.filter(((s) => 'imported' in s && s.imported)).forEach((specifier) => {
  209. // TODO, semver-major: remove `in` check as part of jsdoc->tsdoc migration
  210. checkDeprecation(node, 'imported' in specifier && 'name' in specifier.imported && `${MODULES[node.source.value][0]}.${specifier.imported.name}`, specifier);
  211. });
  212. },
  213. VariableDeclarator(node) {
  214. const reactModuleName = getReactModuleName(node);
  215. const isRequire = node.init
  216. && 'callee' in node.init
  217. && node.init.callee
  218. && 'name' in node.init.callee
  219. && node.init.callee.name === 'require';
  220. const isReactRequire = node.init
  221. && 'arguments' in node.init
  222. && node.init.arguments
  223. && node.init.arguments.length
  224. && typeof MODULES['value' in node.init.arguments[0] ? node.init.arguments[0].value : undefined] !== 'undefined';
  225. const isDestructuring = node.id && node.id.type === 'ObjectPattern';
  226. if (
  227. !(isDestructuring && reactModuleName)
  228. && !(isDestructuring && isRequire && isReactRequire)
  229. ) {
  230. return;
  231. }
  232. ('properties' in node.id ? node.id.properties : undefined).filter((p) => p.type !== 'RestElement' && p.key).forEach((property) => {
  233. checkDeprecation(
  234. node,
  235. 'key' in property && 'name' in property.key && `${reactModuleName || pragma}.${property.key.name}`,
  236. property
  237. );
  238. });
  239. },
  240. ClassDeclaration: checkLifeCycleMethods,
  241. ClassExpression: checkLifeCycleMethods,
  242. ObjectExpression: checkLifeCycleMethods,
  243. };
  244. },
  245. };