eslint-plugin-reblend
Version:
Reblend specific linting rules for ESLint
147 lines (129 loc) • 3.51 kB
JavaScript
/**
* @fileoverview Enforce the use of the 'onChange' or 'readonly' attribute when 'checked' is used'
* @author Jaesoekjjang
*/
;
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(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);
},
};
},
};