PromoCursed/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js
2024-08-20 23:25:37 +04:00

210 lines
5.9 KiB
JavaScript

/**
* @fileoverview Prevents usage of Function.prototype.bind and arrow functions
* in React component props.
* @author Daniel Lo Nigro <dan.cx>
* @author Jacky Ho
*/
'use strict';
const propName = require('jsx-ast-utils/propName');
const docsUrl = require('../util/docsUrl');
const jsxUtil = require('../util/jsx');
const report = require('../util/report');
const getAncestors = require('../util/eslint').getAncestors;
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
const messages = {
bindCall: 'JSX props should not use .bind()',
arrowFunc: 'JSX props should not use arrow functions',
bindExpression: 'JSX props should not use ::',
func: 'JSX props should not use functions',
};
module.exports = {
meta: {
docs: {
description: 'Disallow `.bind()` or arrow functions in JSX props',
category: 'Best Practices',
recommended: false,
url: docsUrl('jsx-no-bind'),
},
messages,
schema: [{
type: 'object',
properties: {
allowArrowFunctions: {
default: false,
type: 'boolean',
},
allowBind: {
default: false,
type: 'boolean',
},
allowFunctions: {
default: false,
type: 'boolean',
},
ignoreRefs: {
default: false,
type: 'boolean',
},
ignoreDOMComponents: {
default: false,
type: 'boolean',
},
},
additionalProperties: false,
}],
},
create(context) {
const configuration = context.options[0] || {};
// Keep track of all the variable names pointing to a bind call,
// bind expression or an arrow function in different block statements
const blockVariableNameSets = {};
/**
* @param {string | number} blockStart
*/
function setBlockVariableNameSet(blockStart) {
blockVariableNameSets[blockStart] = {
arrowFunc: new Set(),
bindCall: new Set(),
bindExpression: new Set(),
func: new Set(),
};
}
function getNodeViolationType(node) {
const nodeType = node.type;
if (
!configuration.allowBind
&& nodeType === 'CallExpression'
&& node.callee.type === 'MemberExpression'
&& node.callee.property.type === 'Identifier'
&& node.callee.property.name === 'bind'
) {
return 'bindCall';
}
if (nodeType === 'ConditionalExpression') {
return getNodeViolationType(node.test)
|| getNodeViolationType(node.consequent)
|| getNodeViolationType(node.alternate);
}
if (!configuration.allowArrowFunctions && nodeType === 'ArrowFunctionExpression') {
return 'arrowFunc';
}
if (
!configuration.allowFunctions
&& (nodeType === 'FunctionExpression' || nodeType === 'FunctionDeclaration')
) {
return 'func';
}
if (!configuration.allowBind && nodeType === 'BindExpression') {
return 'bindExpression';
}
return null;
}
/**
* @param {string | number} violationType
* @param {any} variableName
* @param {string | number} blockStart
*/
function addVariableNameToSet(violationType, variableName, blockStart) {
blockVariableNameSets[blockStart][violationType].add(variableName);
}
function getBlockStatementAncestors(node) {
return getAncestors(context, node).filter(
(ancestor) => ancestor.type === 'BlockStatement'
).reverse();
}
function reportVariableViolation(node, name, blockStart) {
const blockSets = blockVariableNameSets[blockStart];
const violationTypes = Object.keys(blockSets);
return violationTypes.find((type) => {
if (blockSets[type].has(name)) {
report(context, messages[type], type, {
node,
});
return true;
}
return false;
});
}
function findVariableViolation(node, name) {
getBlockStatementAncestors(node).find(
(block) => reportVariableViolation(node, name, block.range[0])
);
}
return {
BlockStatement(node) {
setBlockVariableNameSet(node.range[0]);
},
FunctionDeclaration(node) {
const blockAncestors = getBlockStatementAncestors(node);
const variableViolationType = getNodeViolationType(node);
if (blockAncestors.length > 0 && variableViolationType) {
addVariableNameToSet(variableViolationType, node.id.name, blockAncestors[0].range[0]);
}
},
VariableDeclarator(node) {
if (!node.init) {
return;
}
const blockAncestors = getBlockStatementAncestors(node);
const variableViolationType = getNodeViolationType(node.init);
if (
blockAncestors.length > 0
&& variableViolationType
&& node.parent.kind === 'const' // only support const right now
) {
addVariableNameToSet(
variableViolationType, node.id.name, blockAncestors[0].range[0]
);
}
},
JSXAttribute(node) {
const isRef = configuration.ignoreRefs && propName(node) === 'ref';
if (isRef || !node.value || !node.value.expression) {
return;
}
const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
if (configuration.ignoreDOMComponents && isDOMComponent) {
return;
}
const valueNode = node.value.expression;
const valueNodeType = valueNode.type;
const nodeViolationType = getNodeViolationType(valueNode);
if (valueNodeType === 'Identifier') {
findVariableViolation(node, valueNode.name);
} else if (nodeViolationType) {
report(context, messages[nodeViolationType], nodeViolationType, {
node,
});
}
},
};
},
};