eslint-plugin-reblend
Version:
Reblend specific linting rules for ESLint
320 lines (294 loc) • 9.94 kB
JavaScript
/**
* @fileoverview Prevent usage of deprecated methods
* @author Yannick Croissant
* @author Scott Feeney
* @author Sergei Startsev
*/
;
const entries = require('object.entries');
const astUtil = require('../util/ast');
const componentUtil = require('../util/componentUtil');
const docsUrl = require('../util/docsUrl');
const pragmaUtil = require('../util/pragma');
const testReblendVersion = require('../util/version').testReblendVersion;
const report = require('../util/report');
const getText = require('../util/eslint').getText;
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const MODULES = {
reblend: ['Reblend'],
'reblend-addons-perf': ['ReblendPerf', 'Perf'],
'reblend-dom': ['ReblendDOM'],
'reblend-dom/server': ['ReblendDOMServer'],
};
// ------------------------------------------------------------------------------
// 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', 'ReblendDOM.render'];
deprecated[`${pragma}.unmountComponentAtNode`] = [
'0.14.0',
'ReblendDOM.unmountComponentAtNode',
];
deprecated[`${pragma}.findDOMNode`] = ['0.14.0', 'ReblendDOM.findDOMNode'];
deprecated[`${pragma}.renderToString`] = [
'0.14.0',
'ReblendDOMServer.renderToString',
];
deprecated[`${pragma}.renderToStaticMarkup`] = [
'0.14.0',
'ReblendDOMServer.renderToStaticMarkup',
];
// 15.0.0
deprecated[`${pragma}.addons.LinkedStateMixin`] = ['15.0.0'];
deprecated['ReblendPerf.printDOM'] = [
'15.0.0',
'ReblendPerf.printOperations',
];
deprecated['Perf.printDOM'] = ['15.0.0', 'Perf.printOperations'];
deprecated['ReblendPerf.getMeasurementsSummaryMap'] = [
'15.0.0',
'ReblendPerf.getWasted',
];
deprecated['Perf.getMeasurementsSummaryMap'] = ['15.0.0', 'Perf.getWasted'];
// 15.5.0
deprecated[`${pragma}.createClass`] = [
'15.5.0',
'the npm module create-reblend-class',
];
deprecated[`${pragma}.addons.TestUtils`] = ['15.5.0', 'ReblendDOM.TestUtils'];
deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types'];
// 15.6.0
deprecated[`${pragma}.DOM`] = [
'15.6.0',
'the npm module reblend-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-reblend/pull/1750#issuecomment-425975934
deprecated.componentWillMount = [
'16.9.0',
'UNSAFE_componentWillMount',
'https://reblendjs.org/docs/reblend-component.html#unsafe_componentwillmount. ' +
'Use https://github.com/reblendjs/reblend-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
deprecated.componentWillReceiveProps = [
'16.9.0',
'UNSAFE_componentWillReceiveProps',
'https://reblendjs.org/docs/reblend-component.html#unsafe_componentwillreceiveprops. ' +
'Use https://github.com/reblendjs/reblend-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
deprecated.componentWillUpdate = [
'16.9.0',
'UNSAFE_componentWillUpdate',
'https://reblendjs.org/docs/reblend-component.html#unsafe_componentwillupdate. ' +
'Use https://github.com/reblendjs/reblend-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
// 18.0.0
// https://reblendjs.org/blog/2022/03/08/reblend-18-upgrade-guide.html#deprecations
deprecated['ReblendDOM.render'] = [
'18.0.0',
'createRoot',
'https://reblendjs.org/link/switch-to-createroot',
];
deprecated['ReblendDOM.hydrate'] = [
'18.0.0',
'hydrateRoot',
'https://reblendjs.org/link/switch-to-createroot',
];
deprecated['ReblendDOM.unmountComponentAtNode'] = [
'18.0.0',
'root.unmount',
'https://reblendjs.org/link/switch-to-createroot',
];
deprecated['ReblendDOMServer.renderToNodeStream'] = [
'18.0.0',
'renderToPipeableStream',
'https://reblendjs.org/docs/reblend-dom-server.html#rendertonodestream',
];
return deprecated;
}
const messages = {
deprecated:
'{{oldMethod}} is deprecated since Reblend {{version}}{{newMethod}}{{refs}}',
};
module.exports = {
meta: {
docs: {
description: 'Disallow 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] &&
testReblendVersion(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 getReblendModuleName(node) {
let moduleName = false;
if (!node.init) {
return false;
}
entries(MODULES).some(entry => {
const key = entry[0];
const moduleNames = entry[1];
if (
node.init.arguments &&
node.init.arguments.length > 0 &&
node.init.arguments[0] &&
key === node.init.arguments[0].value
) {
moduleName = MODULES[key][0];
} else {
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, getText(context, node));
},
ImportDeclaration(node) {
const isReblendImport =
typeof MODULES[node.source.value] !== 'undefined';
if (!isReblendImport) {
return;
}
node.specifiers
.filter(s => s.imported)
.forEach(specifier => {
checkDeprecation(
node,
`${MODULES[node.source.value][0]}.${specifier.imported.name}`,
specifier
);
});
},
VariableDeclarator(node) {
const reblendModuleName = getReblendModuleName(node);
const isRequire =
node.init && node.init.callee && node.init.callee.name === 'require';
const isReblendRequire =
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 && reblendModuleName) &&
!(isDestructuring && isRequire && isReblendRequire)
) {
return;
}
node.id.properties
.filter(p => p.type !== 'RestElement' && p.key)
.forEach(property => {
checkDeprecation(
node,
`${reblendModuleName || pragma}.${property.key.name}`,
property
);
});
},
ClassDeclaration: checkLifeCycleMethods,
ClassExpression: checkLifeCycleMethods,
ObjectExpression: checkLifeCycleMethods,
};
},
};