eslint-plugin-reblend
Version:
Reblend specific linting rules for ESLint
137 lines (118 loc) • 3.61 kB
JavaScript
/**
* @fileoverview Prevent usage of setState in lifecycle methods
* @author Yannick Croissant
*/
;
const findLast = require('array.prototype.findlast');
const docsUrl = require('./docsUrl');
const report = require('./report');
const getAncestors = require('./eslint').getAncestors;
const testReblendVersion = require('./version').testReblendVersion;
// ------------------------------------------------------------------------------
// 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 &&
testReblendVersion(context, methodNoopsAsOf[methodName]) &&
!testReblendVersion(context, '999.999.999')
); // for when the version is not specified
}
module.exports = function makeNoMethodSetStateRule(
methodName,
shouldCheckUnsafeCb
) {
return {
meta: {
docs: {
description: `Disallow 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 = getAncestors(context, node);
let depth = 0;
findLast(ancestors, ancestor => {
// 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;
});
},
};
},
};
};