securityos/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-o...

142 lines
3.4 KiB
JavaScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
/**
* @fileoverview Enforce the use of the 'onChange' or 'readonly' attribute when 'checked' is used'
* @author Jaesoekjjang
*/
'use strict';
const ASTUtils = require('jsx-ast-utils');
const flatMap = require('array.prototype.flatmap');
const isCreateElement = require('../util/isCreateElement');
const report = require('../util/report');
const docsUrl = require('../util/docsUrl');
const messages = {
missingProperty: '`checked` should be used with either `onChange` or `readOnly`.',
exclusiveCheckedAttribute: 'Use either `checked` or `defaultChecked`, but not both.',
};
const targetPropSet = new Set(['checked', 'onChange', 'readOnly', 'defaultChecked']);
const defaultOptions = {
ignoreMissingProperties: false,
ignoreExclusiveCheckedAttribute: false,
};
/**
* @param {string[]} properties
* @param {string} keyName
* @returns {Set<string>}
*/
function extractTargetProps(properties, keyName) {
return new Set(
flatMap(
properties,
(prop) => (
prop[keyName] && targetPropSet.has(prop[keyName].name)
? [prop[keyName].name]
: []
)
)
);
}
module.exports = {
meta: {
docs: {
description: 'Enforce using `onChange` or `readonly` attribute when `checked` is used',
category: 'Best Practices',
recommended: false,
url: docsUrl('checked-requires-onchange-or-readonly'),
},
messages,
schema: [{
additionalProperties: false,
properties: {
ignoreMissingProperties: {
type: 'boolean',
},
ignoreExclusiveCheckedAttribute: {
type: 'boolean',
},
},
}],
},
create(context) {
const options = Object.assign({}, defaultOptions, context.options[0]);
function reportMissingProperty(node) {
report(
context,
messages.missingProperty,
'missingProperty',
{ node }
);
}
function reportExclusiveCheckedAttribute(node) {
report(
context,
messages.exclusiveCheckedAttribute,
'exclusiveCheckedAttribute',
{ node }
);
}
/**
* @param {ASTNode} node
* @param {Set<string>} propSet
* @returns {void}
*/
const checkAttributesAndReport = (node, propSet) => {
if (!propSet.has('checked')) {
return;
}
if (!options.ignoreExclusiveCheckedAttribute && propSet.has('defaultChecked')) {
reportExclusiveCheckedAttribute(node);
}
if (
!options.ignoreMissingProperties
&& !(propSet.has('onChange') || propSet.has('readOnly'))
) {
reportMissingProperty(node);
}
};
return {
JSXOpeningElement(node) {
if (ASTUtils.elementType(node) !== 'input') {
return;
}
const propSet = extractTargetProps(node.attributes, 'name');
checkAttributesAndReport(node, propSet);
},
CallExpression(node) {
if (!isCreateElement(node, context)) {
return;
}
const firstArg = node.arguments[0];
const secondArg = node.arguments[1];
if (
!firstArg
|| firstArg.type !== 'Literal'
|| firstArg.value !== 'input'
) {
return;
}
if (!secondArg || secondArg.type !== 'ObjectExpression') {
return;
}
const propSet = extractTargetProps(secondArg.properties, 'key');
checkAttributesAndReport(node, propSet);
},
};
},
};