eslint-plugin-react-snob
Version:
An ESLint plugin for React best practices
123 lines (122 loc) • 4.87 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.noComplexJsxConditions = void 0;
const utils_1 = require("@typescript-eslint/utils");
const utils_2 = require("../utils");
function hasComplexTernaryCondition(node) {
// Check if test condition has logical operators
const testLogicalCount = (0, utils_2.countLogicalOperators)(node.test);
// Also check if consequent or alternate are complex ternaries
let consequentComplex = false;
let alternateComplex = false;
if (node.consequent.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
consequentComplex = hasComplexTernaryCondition(node.consequent);
}
if (node.alternate.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
alternateComplex = hasComplexTernaryCondition(node.alternate);
}
return testLogicalCount > 0 || consequentComplex || alternateComplex;
}
function isComplexCondition(node) {
// Count logical operators (&&, ||)
const logicalCount = (0, utils_2.countLogicalOperators)(node);
// Consider complex if more than 2 logical operators (allows simple a && b && c)
if (logicalCount > 2) {
return true;
}
// Check for complex ternary conditions
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
return hasComplexTernaryCondition(node);
}
// For 2 or 3 logical operators, check if operands are complex
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression && logicalCount >= 2) {
return hasComplexOperandsInChain(node);
}
// Check for template literals combined with logical operators (even single logical operator makes it complex)
if (logicalCount >= 1 && (0, utils_2.hasTemplateLiteral)(node)) {
return true;
}
// Check for logical assignment operators
if ((0, utils_2.hasLogicalAssignment)(node)) {
return true;
}
return false;
}
function hasComplexOperandsInChain(node) {
// Check if the logical chain contains complex operands
function checkOperand(operand) {
return (0, utils_2.isComplexOperand)(operand);
}
function traverseLogical(expr) {
if (expr.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
return traverseLogical(expr.left) || traverseLogical(expr.right);
}
return checkOperand(expr);
}
return traverseLogical(node);
}
function isInsideClassNameUtility(node) {
// Check if this JSX expression is inside a className utility function call
const parent = node.parent;
if (parent && parent.type === utils_1.AST_NODE_TYPES.JSXAttribute) {
const jsxAttribute = parent;
if (jsxAttribute.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
jsxAttribute.name.name === 'className' &&
node.expression.type === utils_1.AST_NODE_TYPES.CallExpression) {
const callExpression = node.expression;
if (callExpression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
const functionName = callExpression.callee.name;
// Allow className utility functions
return ['cn', 'clsx', 'cva', 'cx'].includes(functionName);
}
}
}
return false;
}
exports.noComplexJsxConditions = (0, utils_2.createRule)({
create(context) {
const checkedNodes = new Set();
function checkExpression(node) {
if (checkedNodes.has(node)) {
return;
}
checkedNodes.add(node);
if (node.expression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
return;
}
// Skip complex condition checking if inside className utility functions
if (isInsideClassNameUtility(node)) {
return;
}
if (isComplexCondition(node.expression)) {
context.report({
messageId: 'complexCondition',
node: node,
});
}
}
return {
JSXAttribute(node) {
if (node.value && node.value.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
checkExpression(node.value);
}
},
JSXExpressionContainer(node) {
checkExpression(node);
},
};
},
defaultOptions: [],
meta: {
docs: {
description: 'Disallow complex boolean conditions in JSX expressions and component props',
},
fixable: undefined,
messages: {
complexCondition: 'Complex boolean condition found in JSX. Extract to a descriptive variable (e.g., _isReady, _canEdit) to improve readability.',
},
schema: [],
type: 'suggestion',
},
name: 'no-complex-jsx-conditions',
});
;