You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
8.2 KiB

2 years ago
/**
* @fileoverview Prevent usage of deprecated methods
* @author Yannick Croissant
* @author Scott Feeney
* @author Sergei Startsev
*/
'use strict';
const values = require('object.values');
const astUtil = require('../util/ast');
const componentUtil = require('../util/componentUtil');
const docsUrl = require('../util/docsUrl');
const pragmaUtil = require('../util/pragma');
const testReactVersion = require('../util/version').testReactVersion;
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const MODULES = {
react: ['React'],
'react-addons-perf': ['ReactPerf', 'Perf'],
};
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function getDeprecated(pragma) {
const deprecated = {};
// 0.12.0
deprecated[`${pragma}.renderComponent`] = ['0.12.0', `${pragma}.render`];
deprecated[`${pragma}.renderComponentToString`] = ['0.12.0', `${pragma}.renderToString`];
deprecated[`${pragma}.renderComponentToStaticMarkup`] = ['0.12.0', `${pragma}.renderToStaticMarkup`];
deprecated[`${pragma}.isValidComponent`] = ['0.12.0', `${pragma}.isValidElement`];
deprecated[`${pragma}.PropTypes.component`] = ['0.12.0', `${pragma}.PropTypes.element`];
deprecated[`${pragma}.PropTypes.renderable`] = ['0.12.0', `${pragma}.PropTypes.node`];
deprecated[`${pragma}.isValidClass`] = ['0.12.0'];
deprecated['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})'];
// 0.13.0
deprecated[`${pragma}.addons.classSet`] = ['0.13.0', 'the npm module classnames'];
deprecated[`${pragma}.addons.cloneWithProps`] = ['0.13.0', `${pragma}.cloneElement`];
// 0.14.0
deprecated[`${pragma}.render`] = ['0.14.0', 'ReactDOM.render'];
deprecated[`${pragma}.unmountComponentAtNode`] = ['0.14.0', 'ReactDOM.unmountComponentAtNode'];
deprecated[`${pragma}.findDOMNode`] = ['0.14.0', 'ReactDOM.findDOMNode'];
deprecated[`${pragma}.renderToString`] = ['0.14.0', 'ReactDOMServer.renderToString'];
deprecated[`${pragma}.renderToStaticMarkup`] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup'];
// 15.0.0
deprecated[`${pragma}.addons.LinkedStateMixin`] = ['15.0.0'];
deprecated['ReactPerf.printDOM'] = ['15.0.0', 'ReactPerf.printOperations'];
deprecated['Perf.printDOM'] = ['15.0.0', 'Perf.printOperations'];
deprecated['ReactPerf.getMeasurementsSummaryMap'] = ['15.0.0', 'ReactPerf.getWasted'];
deprecated['Perf.getMeasurementsSummaryMap'] = ['15.0.0', 'Perf.getWasted'];
// 15.5.0
deprecated[`${pragma}.createClass`] = ['15.5.0', 'the npm module create-react-class'];
deprecated[`${pragma}.addons.TestUtils`] = ['15.5.0', 'ReactDOM.TestUtils'];
deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types'];
// 15.6.0
deprecated[`${pragma}.DOM`] = ['15.6.0', 'the npm module react-dom-factories'];
// 16.9.0
// For now the following life-cycle methods are just legacy, not deprecated:
// `componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate`
// https://github.com/yannickcr/eslint-plugin-react/pull/1750#issuecomment-425975934
deprecated.componentWillMount = [
'16.9.0',
'UNSAFE_componentWillMount',
'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount. '
+ 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
deprecated.componentWillReceiveProps = [
'16.9.0',
'UNSAFE_componentWillReceiveProps',
'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops. '
+ 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
deprecated.componentWillUpdate = [
'16.9.0',
'UNSAFE_componentWillUpdate',
'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate. '
+ 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
return deprecated;
}
const messages = {
deprecated: '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}{{refs}}',
};
module.exports = {
meta: {
docs: {
description: 'Prevent usage of deprecated methods',
category: 'Best Practices',
recommended: true,
url: docsUrl('no-deprecated'),
},
messages,
schema: [],
},
create(context) {
const pragma = pragmaUtil.getFromContext(context);
const deprecated = getDeprecated(pragma);
function isDeprecated(method) {
return (
deprecated
&& deprecated[method]
&& deprecated[method][0]
&& testReactVersion(context, `>= ${deprecated[method][0]}`)
);
}
function checkDeprecation(node, methodName, methodNode) {
if (!isDeprecated(methodName)) {
return;
}
const version = deprecated[methodName][0];
const newMethod = deprecated[methodName][1];
const refs = deprecated[methodName][2];
report(context, messages.deprecated, 'deprecated', {
node: methodNode || node,
data: {
oldMethod: methodName,
version,
newMethod: newMethod ? `, use ${newMethod} instead` : '',
refs: refs ? `, see ${refs}` : '',
},
});
}
function getReactModuleName(node) {
let moduleName = false;
if (!node.init) {
return moduleName;
}
values(MODULES).some((moduleNames) => {
moduleName = moduleNames.find((name) => name === node.init.name);
return moduleName;
});
return moduleName;
}
/**
* Returns life cycle methods if available
* @param {ASTNode} node The AST node being checked.
* @returns {Array} The array of methods.
*/
function getLifeCycleMethods(node) {
const properties = astUtil.getComponentProperties(node);
return properties.map((property) => ({
name: astUtil.getPropertyName(property),
node: astUtil.getPropertyNameNode(property),
}));
}
/**
* Checks life cycle methods
* @param {ASTNode} node The AST node being checked.
*/
function checkLifeCycleMethods(node) {
if (
componentUtil.isES5Component(node, context)
|| componentUtil.isES6Component(node, context)
) {
const methods = getLifeCycleMethods(node);
methods.forEach((method) => checkDeprecation(node, method.name, method.node));
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
MemberExpression(node) {
checkDeprecation(node, context.getSourceCode().getText(node));
},
ImportDeclaration(node) {
const isReactImport = typeof MODULES[node.source.value] !== 'undefined';
if (!isReactImport) {
return;
}
node.specifiers.forEach((specifier) => {
if (!specifier.imported) {
return;
}
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`);
});
},
VariableDeclarator(node) {
const reactModuleName = getReactModuleName(node);
const isRequire = node.init && node.init.callee && node.init.callee.name === 'require';
const isReactRequire = node.init
&& node.init.arguments
&& node.init.arguments.length
&& typeof MODULES[node.init.arguments[0].value] !== 'undefined';
const isDestructuring = node.id && node.id.type === 'ObjectPattern';
if (
!(isDestructuring && reactModuleName)
&& !(isDestructuring && isRequire && isReactRequire)
) {
return;
}
node.id.properties.forEach((property) => {
if (property.type !== 'RestElement' && property.key) {
checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`);
}
});
},
ClassDeclaration: checkLifeCycleMethods,
ClassExpression: checkLifeCycleMethods,
ObjectExpression: checkLifeCycleMethods,
};
},
};