@masknet/eslint-plugin
Version:
eslint plugin for masknet
93 lines • 3.42 kB
JavaScript
import { createRule } from "../../rule.js";
const DEFAULT_ELEMENT_LIST = ['a', 'button', 'input', 'textarea'];
const DEFAULT_ATTRIBUTE_LIST = ['editable', 'onInput', 'onChange', 'onClick', 'onKeyDown', 'onKeyUp', 'onSubmit'];
const DEFAULT_IGNORE_ATTRIBUTE_LIST = ['disabled', 'readonly'];
export default createRule({
name: 'jsx/prefer-test-id',
meta: {
type: 'suggestion',
docs: {
description: 'Enforces `data-test-id` attribute is present on interactive DOM elements to help with UI testing',
},
schema: [
{
type: 'object',
properties: {
'id': { type: 'string' },
'elements': { type: 'array', items: { type: 'string' }, uniqueItems: true },
'attributes': { type: 'array', items: { type: 'string' }, uniqueItems: true },
'ignore-attributes': { type: 'array', items: { type: 'string' }, uniqueItems: true },
},
additionalProperties: false,
},
],
messages: {
element: '<{{element}}> elements must have a `{{id}}` attribute',
attribute: '<{{element}}> with an `{{name}}` handler must have a `{{id}}` attribute',
},
},
defaultOptions: [
{
'id': 'data-test-id',
'elements': DEFAULT_ELEMENT_LIST,
'attributes': DEFAULT_ATTRIBUTE_LIST,
'ignore-attributes': DEFAULT_IGNORE_ATTRIBUTE_LIST,
},
],
create(context, [options]) {
const source = context.sourceCode;
return {
JSXOpeningElement(node) {
const element = source.getText(node.name);
const attributes = new Map(node.attributes
.filter((node) => node.type === 'JSXAttribute')
.map(({ name, value }) => [source.getText(name), value]));
if (isValid(attributes.get(options.id)))
return;
if (shouldIgnore(attributes, options['ignore-attributes']))
return;
if (options.elements.includes(element)) {
context.report({
node,
messageId: 'element',
data: { element, id: options.id },
});
}
else {
const name = options.attributes.find((name) => attributes.has(name));
if (!name)
return;
context.report({
node,
messageId: 'attribute',
data: { element, name, id: options.id },
});
}
},
};
},
});
function shouldIgnore(attributes, ignore) {
return ignore.some((name) => {
const node = attributes.get(name);
if (node === null)
return true;
if (node?.type === 'JSXExpressionContainer')
return false;
return isValid(node);
});
}
function isValid(node) {
if (!node)
return false;
switch (node.type) {
case 'Literal': {
return Boolean(node.value);
}
case 'JSXExpressionContainer': {
return true;
}
}
return false;
}
//# sourceMappingURL=prefer-test-id.js.map