123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- /**
- * @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 {object[]} 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]
- : []
- )
- )
- );
- }
- /** @type {import('eslint').Rule.RuleModule} */
- 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(context, node)) {
- 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);
- },
- };
- },
- };
|