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.

120 lines
3.3 KiB

2 years ago
/**
* @fileoverview Prevent usage of setState in lifecycle methods
* @author Yannick Croissant
*/
'use strict';
const docsUrl = require('./docsUrl');
const report = require('./report');
const testReactVersion = require('./version').testReactVersion;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function mapTitle(methodName) {
const map = {
componentDidMount: 'did-mount',
componentDidUpdate: 'did-update',
componentWillUpdate: 'will-update',
};
const title = map[methodName];
if (!title) {
throw Error(`No docsUrl for '${methodName}'`);
}
return `no-${title}-set-state`;
}
const messages = {
noSetState: 'Do not use setState in {{name}}',
};
const methodNoopsAsOf = {
componentDidMount: '>= 16.3.0',
componentDidUpdate: '>= 16.3.0',
};
function shouldBeNoop(context, methodName) {
return methodName in methodNoopsAsOf
&& testReactVersion(context, methodNoopsAsOf[methodName])
&& !testReactVersion(context, '999.999.999'); // for when the version is not specified
}
module.exports = function makeNoMethodSetStateRule(methodName, shouldCheckUnsafeCb) {
return {
meta: {
docs: {
description: `Prevent usage of setState in ${methodName}`,
category: 'Best Practices',
recommended: false,
url: docsUrl(mapTitle(methodName)),
},
messages,
schema: [{
enum: ['disallow-in-func'],
}],
},
create(context) {
const mode = context.options[0] || 'allow-in-func';
function nameMatches(name) {
if (name === methodName) {
return true;
}
if (typeof shouldCheckUnsafeCb === 'function' && shouldCheckUnsafeCb(context)) {
return name === `UNSAFE_${methodName}`;
}
return false;
}
if (shouldBeNoop(context, methodName)) {
return {};
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression(node) {
const callee = node.callee;
if (
callee.type !== 'MemberExpression'
|| callee.object.type !== 'ThisExpression'
|| callee.property.name !== 'setState'
) {
return;
}
const ancestors = context.getAncestors(callee).reverse();
let depth = 0;
ancestors.some((ancestor) => {
if (/Function(Expression|Declaration)$/.test(ancestor.type)) {
depth += 1;
}
if (
(ancestor.type !== 'Property' && ancestor.type !== 'MethodDefinition' && ancestor.type !== 'ClassProperty' && ancestor.type !== 'PropertyDefinition')
|| !nameMatches(ancestor.key.name)
|| (mode !== 'disallow-in-func' && depth > 1)
) {
return false;
}
report(context, messages.noSetState, 'noSetState', {
node: callee,
data: {
name: ancestor.key.name,
},
});
return true;
});
},
};
},
};
};