0.2.0 - Mid migration

This commit is contained in:
Daniel Mason 2022-04-25 14:47:15 +12:00
parent 139e6a915e
commit 7e38fdbd7d
42393 changed files with 5358157 additions and 62 deletions

View file

@ -0,0 +1,60 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _emojiRegex = _interopRequireDefault(require("emoji-regex"));
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
/**
* @fileoverview Enforce emojis are wrapped in <span> and provide screenreader access.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Emojis should be wrapped in <span>, have role="img", and have an accessible description with aria-label or aria-labelledby.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/accessible-emoji.md'
},
deprecated: true,
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var literalChildValue = node.parent.children.find(function (child) {
return child.type === 'Literal' || child.type === 'JSXText';
});
if (literalChildValue && (0, _emojiRegex["default"])().test(literalChildValue.value)) {
var elementIsHidden = (0, _isHiddenFromScreenReader["default"])((0, _jsxAstUtils.elementType)(node), node.attributes);
if (elementIsHidden) {
return; // emoji is decorative
}
var rolePropValue = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'role'));
var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label');
var arialLabelledByProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby');
var hasLabel = ariaLabelProp !== undefined || arialLabelledByProp !== undefined;
var isSpan = (0, _jsxAstUtils.elementType)(node) === 'span';
if (hasLabel === false || rolePropValue !== 'img' || isSpan === false) {
context.report({
node,
message: errorMessage
});
}
}
}
};
}
};

View file

@ -0,0 +1,257 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _hasAccessibleChild = _interopRequireDefault(require("../util/hasAccessibleChild"));
var _isPresentationRole = _interopRequireDefault(require("../util/isPresentationRole"));
/**
* @fileoverview Enforce all elements that require alternative text have it.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var DEFAULT_ELEMENTS = ['img', 'object', 'area', 'input[type="image"]'];
var schema = (0, _schemas.generateObjSchema)({
elements: _schemas.arraySchema,
img: _schemas.arraySchema,
object: _schemas.arraySchema,
area: _schemas.arraySchema,
'input[type="image"]': _schemas.arraySchema
});
var ariaLabelHasValue = function ariaLabelHasValue(prop) {
var value = (0, _jsxAstUtils.getPropValue)(prop);
if (value === undefined) {
return false;
}
if (typeof value === 'string' && value.length === 0) {
return false;
}
return true;
};
var ruleByElement = {
img(context, node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt'); // Missing alt prop error.
if (altProp === undefined) {
if ((0, _isPresentationRole["default"])(nodeType, node.attributes)) {
context.report({
node,
message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.'
});
return;
} // Check for `aria-label` to provide text alternative
// Don't create an error if the attribute is used correctly. But if it
// isn't, suggest that the developer use `alt` instead.
var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label');
if (ariaLabelProp !== undefined) {
if (!ariaLabelHasValue(ariaLabelProp)) {
context.report({
node,
message: 'The aria-label attribute must have a value. The alt attribute is preferred over aria-label for images.'
});
}
return;
} // Check for `aria-labelledby` to provide text alternative
// Don't create an error if the attribute is used correctly. But if it
// isn't, suggest that the developer use `alt` instead.
var ariaLabelledbyProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby');
if (ariaLabelledbyProp !== undefined) {
if (!ariaLabelHasValue(ariaLabelledbyProp)) {
context.report({
node,
message: 'The aria-labelledby attribute must have a value. The alt attribute is preferred over aria-labelledby for images.'
});
}
return;
}
context.report({
node,
message: "".concat(nodeType, " elements must have an alt prop, either with meaningful text, or an empty string for decorative images.")
});
return;
} // Check if alt prop is undefined.
var altValue = (0, _jsxAstUtils.getPropValue)(altProp);
var isNullValued = altProp.value === null; // <img alt />
if (altValue && !isNullValued || altValue === '') {
return;
} // Undefined alt prop error.
context.report({
node,
message: "Invalid alt value for ".concat(nodeType, ". Use alt=\"\" for presentational images.")
});
},
object(context, node) {
var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label');
var arialLabelledByProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby');
var hasLabel = ariaLabelHasValue(ariaLabelProp) || ariaLabelHasValue(arialLabelledByProp);
var titleProp = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'title'));
var hasTitleAttr = !!titleProp;
if (hasLabel || hasTitleAttr || (0, _hasAccessibleChild["default"])(node.parent)) {
return;
}
context.report({
node,
message: 'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby props.'
});
},
area(context, node) {
var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label');
var arialLabelledByProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby');
var hasLabel = ariaLabelHasValue(ariaLabelProp) || ariaLabelHasValue(arialLabelledByProp);
if (hasLabel) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
if (altProp === undefined) {
context.report({
node,
message: 'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
});
return;
}
var altValue = (0, _jsxAstUtils.getPropValue)(altProp);
var isNullValued = altProp.value === null; // <area alt />
if (altValue && !isNullValued || altValue === '') {
return;
}
context.report({
node,
message: 'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
});
},
'input[type="image"]': function inputImage(context, node) {
// Only test input[type="image"]
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (nodeType === 'input') {
var typePropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'type'));
if (typePropValue !== 'image') {
return;
}
}
var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label');
var arialLabelledByProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby');
var hasLabel = ariaLabelHasValue(ariaLabelProp) || ariaLabelHasValue(arialLabelledByProp);
if (hasLabel) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
if (altProp === undefined) {
context.report({
node,
message: '<input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
});
return;
}
var altValue = (0, _jsxAstUtils.getPropValue)(altProp);
var isNullValued = altProp.value === null; // <area alt />
if (altValue && !isNullValued || altValue === '') {
return;
}
context.report({
node,
message: '<input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
});
}
};
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/alt-text.md'
},
schema: [schema]
},
create: function create(context) {
var _ref;
var options = context.options[0] || {}; // Elements to validate for alt text.
var elementOptions = options.elements || DEFAULT_ELEMENTS; // Get custom components for just the elements that will be tested.
var customComponents = elementOptions.map(function (element) {
return options[element];
}).reduce(function (components, customComponentsForElement) {
return components.concat(customComponentsForElement || []);
}, []);
var typesToValidate = new Set((_ref = []).concat.apply(_ref, [customComponents].concat((0, _toConsumableArray2["default"])(elementOptions))).map(function (type) {
if (type === 'input[type="image"]') {
return 'input';
}
return type;
}));
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (!typesToValidate.has(nodeType)) {
return;
}
var DOMElement = nodeType;
if (DOMElement === 'input') {
DOMElement = 'input[type="image"]';
} // Map nodeType to the DOM element if we are running this on a custom component.
if (elementOptions.indexOf(DOMElement) === -1) {
DOMElement = elementOptions.find(function (element) {
var customComponentsForElement = options[element] || [];
return customComponentsForElement.indexOf(nodeType) > -1;
});
}
ruleByElement[DOMElement](context, node);
}
};
}
};

View file

@ -0,0 +1,52 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _hasAccessibleChild = _interopRequireDefault(require("../util/hasAccessibleChild"));
/**
* @fileoverview Enforce anchor elements to contain accessible content.
* @author Lisa Ring & Niklas Holmberg
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Anchors must have content and the content must be accessible by a screen reader.';
var schema = (0, _schemas.generateObjSchema)({
components: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/anchor-has-content.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var componentOptions = options.components || [];
var typeCheck = ['a'].concat(componentOptions);
var nodeType = (0, _jsxAstUtils.elementType)(node); // Only check anchor elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
if ((0, _hasAccessibleChild["default"])(node.parent)) {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,115 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Performs validity check on anchor hrefs. Warns when anchors are used as buttons.
* @author Almero Steyn
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var allAspects = ['noHref', 'invalidHref', 'preferButton'];
var preferButtonErrorMessage = 'Anchor used as a button. Anchors are primarily expected to navigate. Use the button element instead. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md';
var noHrefErrorMessage = 'The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md';
var invalidHrefErrorMessage = 'The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value. If you cannot provide a valid href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md';
var schema = (0, _schemas.generateObjSchema)({
components: _schemas.arraySchema,
specialLink: _schemas.arraySchema,
aspects: (0, _schemas.enumArraySchema)(allAspects, 1)
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/anchor-is-valid.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
var options = context.options[0] || {};
var componentOptions = options.components || [];
var typeCheck = ['a'].concat(componentOptions);
var nodeType = (0, _jsxAstUtils.elementType)(node); // Only check anchor elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
} // Set up the rule aspects to check.
var aspects = options.aspects || allAspects; // Create active aspect flag object. Failing checks will only report
// if the related flag is set to true.
var activeAspects = {};
allAspects.forEach(function (aspect) {
activeAspects[aspect] = aspects.indexOf(aspect) !== -1;
});
var propOptions = options.specialLink || [];
var propsToValidate = ['href'].concat(propOptions);
var values = propsToValidate.map(function (prop) {
return (0, _jsxAstUtils.getProp)(node.attributes, prop);
}).map(function (prop) {
return (0, _jsxAstUtils.getPropValue)(prop);
}); // Checks if any actual or custom href prop is provided.
var hasAnyHref = values.filter(function (value) {
return value === undefined || value === null;
}).length !== values.length; // Need to check for spread operator as props can be spread onto the element
// leading to an incorrect validation error.
var hasSpreadOperator = attributes.filter(function (prop) {
return prop.type === 'JSXSpreadAttribute';
}).length > 0;
var onClick = (0, _jsxAstUtils.getProp)(attributes, 'onClick'); // When there is no href at all, specific scenarios apply:
if (!hasAnyHref) {
// If no spread operator is found and no onClick event is present
// it is a link without href.
if (!hasSpreadOperator && activeAspects.noHref && (!onClick || onClick && !activeAspects.preferButton)) {
context.report({
node,
message: noHrefErrorMessage
});
} // If no spread operator is found but an onClick is preset it should be a button.
if (!hasSpreadOperator && onClick && activeAspects.preferButton) {
context.report({
node,
message: preferButtonErrorMessage
});
}
return;
} // Hrefs have been found, now check for validity.
var invalidHrefValues = values.filter(function (value) {
return value !== undefined && value !== null;
}).filter(function (value) {
return typeof value === 'string' && (!value.length || value === '#' || /^\W*?javascript:/.test(value));
});
if (invalidHrefValues.length !== 0) {
// If an onClick is found it should be a button, otherwise it is an invalid link.
if (onClick && activeAspects.preferButton) {
context.report({
node,
message: preferButtonErrorMessage
});
} else if (activeAspects.invalidHref) {
context.report({
node,
message: invalidHrefErrorMessage
});
}
}
}
};
}
};

View file

@ -0,0 +1,69 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _getTabIndex = _interopRequireDefault(require("../util/getTabIndex"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
/**
* @fileoverview Enforce elements with aria-activedescendant are tabbable.
* @author Jesse Beach <@jessebeach>
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'An element that manages focus with `aria-activedescendant` must have a tabindex';
var schema = (0, _schemas.generateObjSchema)();
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/aria-activedescendant-has-tabindex.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
if ((0, _jsxAstUtils.getProp)(attributes, 'aria-activedescendant') === undefined) {
return;
}
var type = (0, _jsxAstUtils.elementType)(node); // Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (domElements.indexOf(type) === -1) {
return;
}
var tabIndex = (0, _getTabIndex["default"])((0, _jsxAstUtils.getProp)(attributes, 'tabIndex')); // If this is an interactive element and the tabindex attribute is not specified,
// or the tabIndex property was not mutated, then the tabIndex
// property will be undefined.
if ((0, _isInteractiveElement["default"])(type, attributes) && tabIndex === undefined) {
return;
}
if (tabIndex >= -1) {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,63 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _getSuggestion = _interopRequireDefault(require("../util/getSuggestion"));
/**
* @fileoverview Enforce all aria-* properties are valid.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var ariaAttributes = (0, _toConsumableArray2["default"])(_ariaQuery.aria.keys());
var errorMessage = function errorMessage(name) {
var suggestions = (0, _getSuggestion["default"])(name, ariaAttributes);
var message = "".concat(name, ": This attribute is an invalid ARIA attribute.");
if (suggestions.length > 0) {
return "".concat(message, " Did you mean to use ").concat(suggestions, "?");
}
return message;
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/aria-props.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute); // `aria` needs to be prefix of property.
if (name.indexOf('aria-') !== 0) {
return;
}
var isValid = ariaAttributes.indexOf(name) > -1;
if (isValid === false) {
context.report({
node: attribute,
message: errorMessage(name)
});
}
}
};
}
};

View file

@ -0,0 +1,124 @@
"use strict";
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce ARIA state and property values are valid.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = function errorMessage(name, type, permittedValues) {
switch (type) {
case 'tristate':
return "The value for ".concat(name, " must be a boolean or the string \"mixed\".");
case 'token':
return "The value for ".concat(name, " must be a single token from the following: ").concat(permittedValues, ".");
case 'tokenlist':
return "The value for ".concat(name, " must be a list of one or more tokens from the following: ").concat(permittedValues, ".");
case 'idlist':
return "The value for ".concat(name, " must be a list of strings that represent DOM element IDs (idlist)");
case 'id':
return "The value for ".concat(name, " must be a string that represents a DOM element ID");
case 'boolean':
case 'string':
case 'integer':
case 'number':
default:
return "The value for ".concat(name, " must be a ").concat(type, ".");
}
};
var validityCheck = function validityCheck(value, expectedType, permittedValues) {
switch (expectedType) {
case 'boolean':
return typeof value === 'boolean';
case 'string':
case 'id':
return typeof value === 'string';
case 'tristate':
return typeof value === 'boolean' || value === 'mixed';
case 'integer':
case 'number':
// Booleans resolve to 0/1 values so hard check that it's not first.
// eslint-disable-next-line no-restricted-globals
return typeof value !== 'boolean' && isNaN(Number(value)) === false;
case 'token':
return permittedValues.indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1;
case 'idlist':
return typeof value === 'string' && value.split(' ').every(function (token) {
return validityCheck(token, 'id', []);
});
case 'tokenlist':
return typeof value === 'string' && value.split(' ').every(function (token) {
return permittedValues.indexOf(token.toLowerCase()) > -1;
});
default:
return false;
}
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
validityCheck,
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/aria-proptypes.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name.toLowerCase(); // Not a valid aria-* state or property.
if (normalizedName.indexOf('aria-') !== 0 || _ariaQuery.aria.get(normalizedName) === undefined) {
return;
} // Ignore the attribute if its value is null or undefined.
if ((0, _jsxAstUtils.getPropValue)(attribute) == null) return;
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute); // Ignore the attribute if its value is not a literal.
if (value === null) {
return;
} // These are the attributes of the property/state to check against.
var attributes = _ariaQuery.aria.get(normalizedName);
var permittedType = attributes.type;
var allowUndefined = attributes.allowUndefined || false;
var permittedValues = attributes.values || [];
var isValid = validityCheck(value, permittedType, permittedValues) || allowUndefined && value === undefined;
if (isValid) {
return;
}
context.report({
node: attribute,
message: errorMessage(name, permittedType, permittedValues)
});
}
};
}
};

View file

@ -0,0 +1,84 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce aria role attribute is valid.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Elements with ARIA roles must use a valid, non-abstract ARIA role.';
var schema = (0, _schemas.generateObjSchema)({
ignoreNonDOM: {
type: 'boolean',
"default": false
}
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/aria-role.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
// Determine if ignoreNonDOM is set to true
// If true, then do not run rule.
var options = context.options[0] || {};
var ignoreNonDOM = !!options.ignoreNonDOM;
if (ignoreNonDOM) {
var type = (0, _jsxAstUtils.elementType)(attribute.parent);
if (!_ariaQuery.dom.get(type)) {
return;
}
} // Get prop name
var name = (0, _jsxAstUtils.propName)(attribute).toUpperCase();
if (name !== 'ROLE') {
return;
}
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute); // If value is undefined, then the role attribute will be dropped in the DOM.
// If value is null, then getLiteralAttributeValue is telling us that the
// value isn't in the form of a literal.
if (value === undefined || value === null) {
return;
}
var values = String(value).split(' ');
var validRoles = (0, _toConsumableArray2["default"])(_ariaQuery.roles.keys()).filter(function (role) {
return _ariaQuery.roles.get(role)["abstract"] === false;
});
var isValid = values.every(function (val) {
return validRoles.indexOf(val) > -1;
});
if (isValid === true) {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,63 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce that elements that do not support ARIA roles,
* states and properties do not have those attributes.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = function errorMessage(invalidProp) {
return "This element does not support ARIA roles, states and properties. Try removing the prop '".concat(invalidProp, "'.");
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/aria-unsupported-elements.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
var nodeAttrs = _ariaQuery.dom.get(nodeType) || {};
var _nodeAttrs$reserved = nodeAttrs.reserved,
isReservedNodeType = _nodeAttrs$reserved === void 0 ? false : _nodeAttrs$reserved; // If it's not reserved, then it can have aria-* roles, states, and properties
if (isReservedNodeType === false) {
return;
}
var invalidAttributes = (0, _toConsumableArray2["default"])(_ariaQuery.aria.keys()).concat('role');
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
var name = (0, _jsxAstUtils.propName)(prop).toLowerCase();
if (invalidAttributes.indexOf(name) > -1) {
context.report({
node,
message: errorMessage(name)
});
}
});
}
};
}
};

View file

@ -0,0 +1,68 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _axeCore = require("axe-core");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Ensure autocomplete attribute is correct.
* @author Wilco Fiers
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)({
inputComponents: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/autocomplete-valid.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var _options$inputCompone = options.inputComponents,
inputComponents = _options$inputCompone === void 0 ? [] : _options$inputCompone;
var inputTypes = ['input'].concat((0, _toConsumableArray2["default"])(inputComponents));
var elType = (0, _jsxAstUtils.elementType)(node);
var autocomplete = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'autocomplete'));
if (typeof autocomplete !== 'string' || !inputTypes.includes(elType)) {
return;
}
var type = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'type'));
var _runVirtualRule = (0, _axeCore.runVirtualRule)('autocomplete-valid', {
nodeName: 'input',
attributes: {
autocomplete,
// Which autocomplete is valid depends on the input type
type: type === null ? undefined : type
}
}),
violations = _runVirtualRule.violations;
if (violations.length === 0) {
return;
} // Since we only test one rule, with one node, return the message from first (and only) instance of each
context.report({
node,
message: violations[0].nodes[0].all[0].message
});
}
};
}
};

View file

@ -0,0 +1,76 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _schemas = require("../util/schemas");
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isPresentationRole = _interopRequireDefault(require("../util/isPresentationRole"));
/**
* @fileoverview Enforce a clickable non-interactive element has at least 1 keyboard event listener.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Visible, non-interactive elements with click handlers must have at least one keyboard listener.';
var schema = (0, _schemas.generateObjSchema)();
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/click-events-have-key-events.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var props = node.attributes;
if ((0, _jsxAstUtils.getProp)(props, 'onclick') === undefined) {
return;
}
var type = (0, _jsxAstUtils.elementType)(node);
var requiredProps = ['onkeydown', 'onkeyup', 'onkeypress'];
if (!(0, _arrayIncludes["default"])(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
}
if ((0, _isHiddenFromScreenReader["default"])(type, props) || (0, _isPresentationRole["default"])(type, props)) {
return;
}
if ((0, _isInteractiveElement["default"])(type, props)) {
return;
}
if ((0, _jsxAstUtils.hasAnyProp)(props, requiredProps)) {
return;
} // Visible, non-interactive elements with click handlers require one keyboard event listener.
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,109 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _schemas = require("../util/schemas");
var _isDOMElement = _interopRequireDefault(require("../util/isDOMElement"));
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isInteractiveRole = _interopRequireDefault(require("../util/isInteractiveRole"));
var _mayHaveAccessibleLabel = _interopRequireDefault(require("../util/mayHaveAccessibleLabel"));
/**
* @fileoverview Enforce controls are associated with a text label.
* @author Jesse Beach
*
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'A control must be associated with a text label.';
var ignoreList = ['link'];
var schema = (0, _schemas.generateObjSchema)({
labelAttributes: _schemas.arraySchema,
controlComponents: _schemas.arraySchema,
ignoreElements: _schemas.arraySchema,
ignoreRoles: _schemas.arraySchema,
depth: {
description: 'JSX tree depth limit to check for accessible label',
type: 'integer',
minimum: 0
}
});
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
var options = context.options[0] || {};
var _options$labelAttribu = options.labelAttributes,
labelAttributes = _options$labelAttribu === void 0 ? [] : _options$labelAttribu,
_options$controlCompo = options.controlComponents,
controlComponents = _options$controlCompo === void 0 ? [] : _options$controlCompo,
_options$ignoreElemen = options.ignoreElements,
ignoreElements = _options$ignoreElemen === void 0 ? [] : _options$ignoreElemen,
_options$ignoreRoles = options.ignoreRoles,
ignoreRoles = _options$ignoreRoles === void 0 ? [] : _options$ignoreRoles;
var newIgnoreElements = new Set([].concat((0, _toConsumableArray2["default"])(ignoreElements), ignoreList));
var rule = function rule(node) {
var tag = (0, _jsxAstUtils.elementType)(node.openingElement);
var role = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.openingElement.attributes, 'role')); // Ignore interactive elements that might get their label from a source
// that cannot be discerned from static analysis, like
// <label><input />Save</label>
if (newIgnoreElements.has(tag)) {
return;
} // Ignore roles that are "interactive" but should not require a label.
if ((0, _arrayIncludes["default"])(ignoreRoles, role)) {
return;
}
var props = node.openingElement.attributes;
var nodeIsDOMElement = (0, _isDOMElement["default"])(tag);
var nodeIsHiddenFromScreenReader = (0, _isHiddenFromScreenReader["default"])(tag, props);
var nodeIsInteractiveElement = (0, _isInteractiveElement["default"])(tag, props);
var nodeIsInteractiveRole = (0, _isInteractiveRole["default"])(tag, props);
var nodeIsControlComponent = controlComponents.indexOf(tag) > -1;
if (nodeIsHiddenFromScreenReader) {
return;
}
var hasAccessibleLabel = true;
if (nodeIsInteractiveElement || nodeIsDOMElement && nodeIsInteractiveRole || nodeIsControlComponent) {
// Prevent crazy recursion.
var recursionDepth = Math.min(options.depth === undefined ? 2 : options.depth, 25);
hasAccessibleLabel = (0, _mayHaveAccessibleLabel["default"])(node, recursionDepth, labelAttributes);
}
if (!hasAccessibleLabel) {
context.report({
node: node.openingElement,
message: errorMessage
});
}
}; // Create visitor selectors.
return {
JSXElement: rule
};
}
};

View file

@ -0,0 +1,59 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _hasAccessibleChild = _interopRequireDefault(require("../util/hasAccessibleChild"));
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
/**
* @fileoverview Enforce heading (h1, h2, etc) elements contain accessible content.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Headings must have content and the content must be accessible by a screen reader.';
var headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
var schema = (0, _schemas.generateObjSchema)({
components: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/heading-has-content.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var componentOptions = options.components || [];
var typeCheck = headings.concat(componentOptions);
var nodeType = (0, _jsxAstUtils.elementType)(node); // Only check 'h*' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
if ((0, _hasAccessibleChild["default"])(node.parent)) {
return;
}
if ((0, _isHiddenFromScreenReader["default"])(nodeType, node.attributes)) {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,45 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce html element has lang prop.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = '<html> elements must have the lang prop.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/html-has-lang.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
if (type && type !== 'html') {
return;
}
var lang = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'lang'));
if (lang) {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,45 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce iframe elements have a title attribute.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = '<iframe> elements must have a unique title property.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/iframe-has-title.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
if (type && type !== 'iframe') {
return;
}
var title = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'title'));
if (title && typeof title === 'string') {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,68 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
/**
* @fileoverview Enforce img alt attribute does not have the word image, picture, or photo.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var REDUNDANT_WORDS = ['image', 'photo', 'picture'];
var errorMessage = 'Redundant alt attribute. Screen-readers already announce `img` tags as an image. You dont need to use the words `image`, `photo,` or `picture` (or any specified custom words) in the alt prop.';
var schema = (0, _schemas.generateObjSchema)({
components: _schemas.arraySchema,
words: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/img-redundant-alt.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var componentOptions = options.components || [];
var typesToValidate = ['img'].concat(componentOptions);
var nodeType = (0, _jsxAstUtils.elementType)(node); // Only check 'label' elements and custom types.
if (typesToValidate.indexOf(nodeType) === -1) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt'); // Return if alt prop is not present.
if (altProp === undefined) {
return;
}
var value = (0, _jsxAstUtils.getLiteralPropValue)(altProp);
var isVisible = (0, _isHiddenFromScreenReader["default"])(nodeType, node.attributes) === false;
var _options$words = options.words,
words = _options$words === void 0 ? [] : _options$words;
var redundantWords = REDUNDANT_WORDS.concat(words);
if (typeof value === 'string' && isVisible) {
var hasRedundancy = new RegExp("(?!{)\\b(".concat(redundantWords.join('|'), ")\\b(?!})"), 'i').test(value);
if (hasRedundancy === true) {
context.report({
node,
message: errorMessage
});
}
}
}
};
}
};

View file

@ -0,0 +1,99 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _schemas = require("../util/schemas");
var _isDisabledElement = _interopRequireDefault(require("../util/isDisabledElement"));
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isInteractiveRole = _interopRequireDefault(require("../util/isInteractiveRole"));
var _isNonInteractiveElement = _interopRequireDefault(require("../util/isNonInteractiveElement"));
var _isNonInteractiveRole = _interopRequireDefault(require("../util/isNonInteractiveRole"));
var _isPresentationRole = _interopRequireDefault(require("../util/isPresentationRole"));
var _getTabIndex = _interopRequireDefault(require("../util/getTabIndex"));
/**
* @fileoverview Enforce that elements with onClick handlers must be tabbable.
* @author Ethan Cohen
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)({
tabbable: (0, _schemas.enumArraySchema)((0, _toConsumableArray2["default"])(_ariaQuery.roles.keys()).filter(function (name) {
return !_ariaQuery.roles.get(name)["abstract"];
}).filter(function (name) {
return _ariaQuery.roles.get(name).superClass.some(function (klasses) {
return (0, _arrayIncludes["default"])(klasses, 'widget');
});
}))
});
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
var interactiveProps = [].concat((0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.mouse), (0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.keyboard));
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/interactive-supports-focus.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var tabbable = context.options && context.options[0] && context.options[0].tabbable || [];
var attributes = node.attributes;
var type = (0, _jsxAstUtils.elementType)(node);
var hasInteractiveProps = (0, _jsxAstUtils.hasAnyProp)(attributes, interactiveProps);
var hasTabindex = (0, _getTabIndex["default"])((0, _jsxAstUtils.getProp)(attributes, 'tabIndex')) !== undefined;
if (!(0, _arrayIncludes["default"])(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
}
if (!hasInteractiveProps || (0, _isDisabledElement["default"])(attributes) || (0, _isHiddenFromScreenReader["default"])(type, attributes) || (0, _isPresentationRole["default"])(type, attributes)) {
// Presentation is an intentional signal from the author that this
// element is not meant to be perceivable. For example, a click screen
// to close a dialog .
return;
}
if (hasInteractiveProps && (0, _isInteractiveRole["default"])(type, attributes) && !(0, _isInteractiveElement["default"])(type, attributes) && !(0, _isNonInteractiveElement["default"])(type, attributes) && !(0, _isNonInteractiveRole["default"])(type, attributes) && !hasTabindex) {
var role = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(attributes, 'role'));
if ((0, _arrayIncludes["default"])(tabbable, role)) {
// Always tabbable, tabIndex = 0
context.report({
node,
message: "Elements with the '".concat(role, "' interactive role must be tabbable.")
});
} else {
// Focusable, tabIndex = -1 or 0
context.report({
node,
message: "Elements with the '".concat(role, "' interactive role must be focusable.")
});
}
}
}
};
}
};

View file

@ -0,0 +1,118 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _mayContainChildComponent = _interopRequireDefault(require("../util/mayContainChildComponent"));
var _mayHaveAccessibleLabel = _interopRequireDefault(require("../util/mayHaveAccessibleLabel"));
/**
* @fileoverview Enforce label tags have an associated control.
* @author Jesse Beach
*
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'A form label must be associated with a control.';
var schema = (0, _schemas.generateObjSchema)({
labelComponents: _schemas.arraySchema,
labelAttributes: _schemas.arraySchema,
controlComponents: _schemas.arraySchema,
assert: {
description: 'Assert that the label has htmlFor, a nested label, both or either',
type: 'string',
"enum": ['htmlFor', 'nesting', 'both', 'either']
},
depth: {
description: 'JSX tree depth limit to check for accessible label',
type: 'integer',
minimum: 0
}
});
var validateId = function validateId(node) {
var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
return htmlForAttr !== false && !!htmlForValue;
};
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
var options = context.options[0] || {};
var labelComponents = options.labelComponents || [];
var assertType = options.assert || 'either';
var componentNames = ['label'].concat(labelComponents);
var rule = function rule(node) {
if (componentNames.indexOf((0, _jsxAstUtils.elementType)(node.openingElement)) === -1) {
return;
}
var controlComponents = ['input', 'meter', 'output', 'progress', 'select', 'textarea'].concat(options.controlComponents || []); // Prevent crazy recursion.
var recursionDepth = Math.min(options.depth === undefined ? 2 : options.depth, 25);
var hasLabelId = validateId(node.openingElement); // Check for multiple control components.
var hasNestedControl = controlComponents.some(function (name) {
return (0, _mayContainChildComponent["default"])(node, name, recursionDepth);
});
var hasAccessibleLabel = (0, _mayHaveAccessibleLabel["default"])(node, recursionDepth, options.labelAttributes);
if (hasAccessibleLabel) {
switch (assertType) {
case 'htmlFor':
if (hasLabelId) {
return;
}
break;
case 'nesting':
if (hasNestedControl) {
return;
}
break;
case 'both':
if (hasLabelId && hasNestedControl) {
return;
}
break;
case 'either':
if (hasLabelId || hasNestedControl) {
return;
}
break;
default:
break;
}
} // htmlFor case
context.report({
node: node.openingElement,
message: errorMessage
});
}; // Create visitor selectors.
return {
JSXElement: rule
};
}
};

View file

@ -0,0 +1,153 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _hasAccessibleChild = _interopRequireDefault(require("../util/hasAccessibleChild"));
/**
* @fileoverview Enforce label tags have htmlFor attribute.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var enumValues = ['nesting', 'id'];
var schema = {
type: 'object',
properties: {
components: _schemas.arraySchema,
required: {
oneOf: [{
type: 'string',
"enum": enumValues
}, (0, _schemas.generateObjSchema)({
some: (0, _schemas.enumArraySchema)(enumValues)
}, ['some']), (0, _schemas.generateObjSchema)({
every: (0, _schemas.enumArraySchema)(enumValues)
}, ['every'])]
},
allowChildren: {
type: 'boolean'
}
}
}; // Breadth-first search, assuming that HTML for forms is shallow.
function validateNesting(node) {
var queue = (0, _toConsumableArray2["default"])(node.parent.children);
var child;
var opener;
while (queue.length) {
child = queue.shift();
opener = child.openingElement;
if (child.type === 'JSXElement' && opener && (opener.name.name === 'input' || opener.name.name === 'textarea' || opener.name.name === 'select')) {
return true;
}
if (child.children) {
queue = queue.concat(child.children);
}
}
return false;
}
var validateId = function validateId(node) {
var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
return htmlForAttr !== false && !!htmlForValue;
};
var validate = function validate(node, required, allowChildren) {
if (allowChildren === true) {
return (0, _hasAccessibleChild["default"])(node.parent);
}
if (required === 'nesting') {
return validateNesting(node);
}
return validateId(node);
};
var getValidityStatus = function getValidityStatus(node, required, allowChildren) {
if (Array.isArray(required.some)) {
var _isValid = required.some.some(function (rule) {
return validate(node, rule, allowChildren);
});
var _message = !_isValid ? "Form label must have ANY of the following types of associated control: ".concat(required.some.join(', ')) : null;
return {
isValid: _isValid,
message: _message
};
}
if (Array.isArray(required.every)) {
var _isValid2 = required.every.every(function (rule) {
return validate(node, rule, allowChildren);
});
var _message2 = !_isValid2 ? "Form label must have ALL of the following types of associated control: ".concat(required.every.join(', ')) : null;
return {
isValid: _isValid2,
message: _message2
};
}
var isValid = validate(node, required, allowChildren);
var message = !isValid ? "Form label must have the following type of associated control: ".concat(required) : null;
return {
isValid,
message
};
};
module.exports = {
meta: {
deprecated: true,
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/label-has-for.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var componentOptions = options.components || [];
var typesToValidate = ['label'].concat(componentOptions);
var nodeType = (0, _jsxAstUtils.elementType)(node); // Only check 'label' elements and custom types.
if (typesToValidate.indexOf(nodeType) === -1) {
return;
}
var required = options.required || {
every: ['nesting', 'id']
};
var allowChildren = options.allowChildren || false;
var _getValidityStatus = getValidityStatus(node, required, allowChildren),
isValid = _getValidityStatus.isValid,
message = _getValidityStatus.message;
if (!isValid) {
context.report({
node,
message
});
}
}
};
}
};

View file

@ -0,0 +1,68 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _jsxAstUtils = require("jsx-ast-utils");
var _languageTags = _interopRequireDefault(require("language-tags"));
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce lang attribute has a valid value.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'lang attribute must have a valid value.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/lang.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(node) {
var name = (0, _jsxAstUtils.propName)(node);
if (name && name.toUpperCase() !== 'LANG') {
return;
}
var parent = node.parent;
var type = (0, _jsxAstUtils.elementType)(parent);
if (type && type !== 'html') {
return;
}
var value = (0, _jsxAstUtils.getLiteralPropValue)(node); // Don't check identifiers
if (value === null) {
return;
}
if (value === undefined) {
context.report({
node,
message: errorMessage
});
return;
}
if (_languageTags["default"].check(value)) {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,98 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview <audio> and <video> elements must have a <track> for captions.
* @author Ethan Cohen
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Media elements such as <audio> and <video> must have a <track> for captions.';
var MEDIA_TYPES = ['audio', 'video'];
var schema = (0, _schemas.generateObjSchema)({
audio: _schemas.arraySchema,
video: _schemas.arraySchema,
track: _schemas.arraySchema
});
var isMediaType = function isMediaType(context, type) {
var options = context.options[0] || {};
return MEDIA_TYPES.map(function (mediaType) {
return options[mediaType];
}).reduce(function (types, customComponent) {
return types.concat(customComponent);
}, MEDIA_TYPES).some(function (typeToCheck) {
return typeToCheck === type;
});
};
var isTrackType = function isTrackType(context, type) {
var options = context.options[0] || {};
return ['track'].concat(options.track || []).some(function (typeToCheck) {
return typeToCheck === type;
});
};
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/media-has-caption.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXElement: function JSXElement(node) {
var element = node.openingElement;
var type = (0, _jsxAstUtils.elementType)(element);
if (!isMediaType(context, type)) {
return;
}
var mutedProp = (0, _jsxAstUtils.getProp)(element.attributes, 'muted');
var mutedPropVal = (0, _jsxAstUtils.getLiteralPropValue)(mutedProp);
if (mutedPropVal === true) {
return;
} // $FlowFixMe https://github.com/facebook/flow/issues/1414
var trackChildren = node.children.filter(function (child) {
if (child.type !== 'JSXElement') {
return false;
} // $FlowFixMe https://github.com/facebook/flow/issues/1414
return isTrackType(context, (0, _jsxAstUtils.elementType)(child.openingElement));
});
if (trackChildren.length === 0) {
context.report({
node: element,
message: errorMessage
});
return;
}
var hasCaption = trackChildren.some(function (track) {
var kindProp = (0, _jsxAstUtils.getProp)(track.openingElement.attributes, 'kind');
var kindPropValue = (0, _jsxAstUtils.getLiteralPropValue)(kindProp) || '';
return kindPropValue.toLowerCase() === 'captions';
});
if (!hasCaption) {
context.report({
node: element,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,71 @@
"use strict";
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce onmouseover/onmouseout are
* accompanied by onfocus/onblur.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var mouseOverErrorMessage = 'onMouseOver must be accompanied by onFocus for accessibility.';
var mouseOutErrorMessage = 'onMouseOut must be accompanied by onBlur for accessibility.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/mouse-events-have-key-events.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var name = node.name.name;
if (!_ariaQuery.dom.get(name)) {
return;
}
var attributes = node.attributes; // Check onmouseover / onfocus pairing.
var onMouseOver = (0, _jsxAstUtils.getProp)(attributes, 'onMouseOver');
var onMouseOverValue = (0, _jsxAstUtils.getPropValue)(onMouseOver);
if (onMouseOver && onMouseOverValue != null) {
var hasOnFocus = (0, _jsxAstUtils.getProp)(attributes, 'onFocus');
var onFocusValue = (0, _jsxAstUtils.getPropValue)(hasOnFocus);
if (hasOnFocus === false || onFocusValue === null || onFocusValue === undefined) {
context.report({
node,
message: mouseOverErrorMessage
});
}
} // Checkout onmouseout / onblur pairing
var onMouseOut = (0, _jsxAstUtils.getProp)(attributes, 'onMouseOut');
var onMouseOutValue = (0, _jsxAstUtils.getPropValue)(onMouseOut);
if (onMouseOut && onMouseOutValue != null) {
var hasOnBlur = (0, _jsxAstUtils.getProp)(attributes, 'onBlur');
var onBlurValue = (0, _jsxAstUtils.getPropValue)(hasOnBlur);
if (hasOnBlur === false || onBlurValue === null || onBlurValue === undefined) {
context.report({
node,
message: mouseOutErrorMessage
});
}
}
}
};
}
};

View file

@ -0,0 +1,38 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce no accesskey attribute on element.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'No access key attribute allowed. Inconsistencies between keyboard shortcuts and keyboard comments used by screenreader and keyboard only users create a11y complications.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-access-key.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var accessKey = (0, _jsxAstUtils.getProp)(node.attributes, 'accesskey');
var accessKeyValue = (0, _jsxAstUtils.getPropValue)(accessKey);
if (accessKey && accessKeyValue) {
context.report({
node,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,56 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _ariaQuery = require("aria-query");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce autoFocus prop is not used.
* @author Ethan Cohen <@evcohen>
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'The autoFocus prop should not be used, as it can reduce usability and accessibility for users.';
var schema = (0, _schemas.generateObjSchema)({
ignoreNonDOM: {
type: 'boolean',
"default": false
}
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-autofocus.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
// Determine if ignoreNonDOM is set to true
// If true, then do not run rule.
var options = context.options[0] || {};
var ignoreNonDOM = !!options.ignoreNonDOM;
if (ignoreNonDOM) {
var type = (0, _jsxAstUtils.elementType)(attribute.parent);
if (!_ariaQuery.dom.get(type)) {
return;
}
} // Don't normalize, since React only recognizes autoFocus on low-level DOM elements.
if ((0, _jsxAstUtils.propName)(attribute) === 'autoFocus') {
context.report({
node: attribute,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,48 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce distracting elements are not used.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = function errorMessage(element) {
return "Do not use <".concat(element, "> elements as they can create visual accessibility issues and are deprecated.");
};
var DEFAULT_ELEMENTS = ['marquee', 'blink'];
var schema = (0, _schemas.generateObjSchema)({
elements: (0, _schemas.enumArraySchema)(DEFAULT_ELEMENTS)
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-distracting-elements.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var elementOptions = options.elements || DEFAULT_ELEMENTS;
var type = (0, _jsxAstUtils.elementType)(node);
var distractingElement = elementOptions.find(function (element) {
return type === element;
});
if (distractingElement) {
context.report({
node,
message: errorMessage(distractingElement)
});
}
}
};
}
};

View file

@ -0,0 +1,87 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _has = _interopRequireDefault(require("has"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isNonInteractiveRole = _interopRequireDefault(require("../util/isNonInteractiveRole"));
var _isPresentationRole = _interopRequireDefault(require("../util/isPresentationRole"));
/**
* @fileoverview Disallow inherently interactive elements to be assigned
* non-interactive roles.
* @author Jesse Beach
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Interactive elements should not be assigned non-interactive roles.';
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-interactive-element-to-noninteractive-role.md'
},
schema: [{
type: 'object',
additionalProperties: {
type: 'array',
items: {
type: 'string'
},
uniqueItems: true
}
}]
},
create: function create(context) {
var options = context.options;
return {
JSXAttribute: function JSXAttribute(attribute) {
var attributeName = (0, _jsxAstUtils.propName)(attribute); // $FlowFixMe: [TODO] Mark propName as a JSXIdentifier, not a string.
if (attributeName !== 'role') {
return;
}
var node = attribute.parent;
var attributes = node.attributes;
var type = (0, _jsxAstUtils.elementType)(node);
var role = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'role'));
if (!(0, _arrayIncludes["default"])(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} // Allow overrides from rule configuration for specific elements and
// roles.
var allowedRoles = options[0] || {};
if ((0, _has["default"])(allowedRoles, type) && (0, _arrayIncludes["default"])(allowedRoles[type], role)) {
return;
}
if ((0, _isInteractiveElement["default"])(type, attributes) && ((0, _isNonInteractiveRole["default"])(type, attributes) || (0, _isPresentationRole["default"])(type, attributes))) {
// Visible, non-interactive elements should not have an interactive handler.
context.report({
node: attribute,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,97 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _has = _interopRequireDefault(require("has"));
var _schemas = require("../util/schemas");
var _isAbstractRole = _interopRequireDefault(require("../util/isAbstractRole"));
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isInteractiveRole = _interopRequireDefault(require("../util/isInteractiveRole"));
var _isNonInteractiveElement = _interopRequireDefault(require("../util/isNonInteractiveElement"));
var _isNonInteractiveRole = _interopRequireDefault(require("../util/isNonInteractiveRole"));
var _isPresentationRole = _interopRequireDefault(require("../util/isPresentationRole"));
/**
* @fileoverview Enforce non-interactive elements have no interactive handlers.
* @author Jese Beach
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Non-interactive elements should not be assigned mouse or keyboard event listeners.';
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
var defaultInteractiveProps = [].concat((0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.focus), (0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.image), (0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.keyboard), (0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.mouse));
var schema = (0, _schemas.generateObjSchema)({
handlers: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-noninteractive-element-interactions.md'
},
schema: [schema]
},
create: function create(context) {
var options = context.options;
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
var type = (0, _jsxAstUtils.elementType)(node);
var config = options[0] || {};
var interactiveProps = config.handlers || defaultInteractiveProps; // Allow overrides from rule configuration for specific elements and roles.
if ((0, _has["default"])(config, type)) {
attributes = attributes.filter(function (attr) {
return attr.type !== 'JSXSpreadAttribute' && !(0, _arrayIncludes["default"])(config[type], (0, _jsxAstUtils.propName)(attr));
});
}
var hasInteractiveProps = interactiveProps.some(function (prop) {
return (0, _jsxAstUtils.hasProp)(attributes, prop) && (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(attributes, prop)) != null;
});
if (!(0, _arrayIncludes["default"])(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
}
if (!hasInteractiveProps || (0, _isHiddenFromScreenReader["default"])(type, attributes) || (0, _isPresentationRole["default"])(type, attributes)) {
// Presentation is an intentional signal from the author that this
// element is not meant to be perceivable. For example, a click screen
// to close a dialog .
return;
}
if ((0, _isInteractiveElement["default"])(type, attributes) || (0, _isInteractiveRole["default"])(type, attributes) || !(0, _isNonInteractiveElement["default"])(type, attributes) && !(0, _isNonInteractiveRole["default"])(type, attributes) || (0, _isAbstractRole["default"])(type, attributes)) {
// This rule has no opinion about abtract roles.
return;
} // Visible, non-interactive elements should not have an interactive handler.
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,86 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _has = _interopRequireDefault(require("has"));
var _getExplicitRole = _interopRequireDefault(require("../util/getExplicitRole"));
var _isNonInteractiveElement = _interopRequireDefault(require("../util/isNonInteractiveElement"));
var _isInteractiveRole = _interopRequireDefault(require("../util/isInteractiveRole"));
/**
* @fileoverview Disallow inherently non-interactive elements to be assigned
* interactive roles.
* @author Jesse Beach
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Non-interactive elements should not be assigned interactive roles.';
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-noninteractive-element-to-interactive-role.md'
},
schema: [{
type: 'object',
additionalProperties: {
type: 'array',
items: {
type: 'string'
},
uniqueItems: true
}
}]
},
create: function create(context) {
var options = context.options;
return {
JSXAttribute: function JSXAttribute(attribute) {
var attributeName = (0, _jsxAstUtils.propName)(attribute); // $FlowFixMe: [TODO] Mark propName as a JSXIdentifier, not a string.
if (attributeName !== 'role') {
return;
}
var node = attribute.parent;
var attributes = node.attributes;
var type = (0, _jsxAstUtils.elementType)(node);
var role = (0, _getExplicitRole["default"])(type, node.attributes);
if (!(0, _arrayIncludes["default"])(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} // Allow overrides from rule configuration for specific elements and
// roles.
var allowedRoles = options[0] || {};
if ((0, _has["default"])(allowedRoles, type) && (0, _arrayIncludes["default"])(allowedRoles[type], role)) {
return;
}
if ((0, _isNonInteractiveElement["default"])(type, attributes) && (0, _isInteractiveRole["default"])(type, attributes)) {
context.report({
node: attribute,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,95 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isInteractiveRole = _interopRequireDefault(require("../util/isInteractiveRole"));
var _isNonLiteralProperty = _interopRequireDefault(require("../util/isNonLiteralProperty"));
var _schemas = require("../util/schemas");
var _getTabIndex = _interopRequireDefault(require("../util/getTabIndex"));
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
var errorMessage = '`tabIndex` should only be declared on interactive elements.';
var schema = (0, _schemas.generateObjSchema)({
roles: _objectSpread(_objectSpread({}, _schemas.arraySchema), {}, {
description: 'An array of ARIA roles'
}),
tags: _objectSpread(_objectSpread({}, _schemas.arraySchema), {}, {
description: 'An array of HTML tag names'
})
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-noninteractive-tabindex.md'
},
schema: [schema]
},
create: function create(context) {
var options = context.options;
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
var attributes = node.attributes;
var tabIndexProp = (0, _jsxAstUtils.getProp)(attributes, 'tabIndex');
var tabIndex = (0, _getTabIndex["default"])(tabIndexProp); // Early return;
if (typeof tabIndex === 'undefined') {
return;
}
var role = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'role'));
if (!_ariaQuery.dom.has(type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} // Allow for configuration overrides.
var _ref = options[0] || {},
tags = _ref.tags,
roles = _ref.roles,
allowExpressionValues = _ref.allowExpressionValues;
if (tags && (0, _arrayIncludes["default"])(tags, type)) {
return;
}
if (roles && (0, _arrayIncludes["default"])(roles, role)) {
return;
}
if (allowExpressionValues === true && (0, _isNonLiteralProperty["default"])(attributes, 'role')) {
return;
}
if ((0, _isInteractiveElement["default"])(type, attributes) || (0, _isInteractiveRole["default"])(type, attributes)) {
return;
}
if (tabIndex >= 0) {
context.report({
node: tabIndexProp,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,45 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce usage of onBlur over onChange for accessibility.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'onBlur must be used instead of onchange, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.';
var applicableTypes = ['select', 'option'];
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-onchange.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (applicableTypes.indexOf(nodeType) === -1) {
return;
}
var onChange = (0, _jsxAstUtils.getProp)(node.attributes, 'onChange');
var hasOnBlur = (0, _jsxAstUtils.getProp)(node.attributes, 'onBlur') !== undefined;
if (onChange && !hasOnBlur) {
context.report({
node,
message: errorMessage
});
}
}
};
}
};

View file

@ -0,0 +1,81 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _has = _interopRequireDefault(require("has"));
var _getExplicitRole = _interopRequireDefault(require("../util/getExplicitRole"));
var _getImplicitRole = _interopRequireDefault(require("../util/getImplicitRole"));
/**
* @fileoverview Enforce explicit role property is not the
* same as implicit/default role property on element.
* @author Ethan Cohen <@evcohen>
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = function errorMessage(element, implicitRole) {
return "The element ".concat(element, " has an implicit role of ").concat(implicitRole, ". Defining this explicitly is redundant and should be avoided.");
};
var DEFAULT_ROLE_EXCEPTIONS = {
nav: ['navigation']
};
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-redundant-roles.md'
},
schema: [{
type: 'object',
additionalProperties: {
type: 'array',
items: {
type: 'string'
},
uniqueItems: true
}
}]
},
create: function create(context) {
var options = context.options;
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
var implicitRole = (0, _getImplicitRole["default"])(type, node.attributes);
var explicitRole = (0, _getExplicitRole["default"])(type, node.attributes);
if (!implicitRole || !explicitRole) {
return;
}
if (implicitRole === explicitRole) {
var allowedRedundantRoles = options[0] || {};
var redundantRolesForElement;
if ((0, _has["default"])(allowedRedundantRoles, type)) {
redundantRolesForElement = allowedRedundantRoles[type];
} else {
redundantRolesForElement = DEFAULT_ROLE_EXCEPTIONS[type] || [];
}
if ((0, _arrayIncludes["default"])(redundantRolesForElement, implicitRole)) {
return;
}
context.report({
node,
message: errorMessage(type, implicitRole.toLowerCase())
});
}
}
};
}
};

View file

@ -0,0 +1,99 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _arrayIncludes = _interopRequireDefault(require("array-includes"));
var _schemas = require("../util/schemas");
var _isAbstractRole = _interopRequireDefault(require("../util/isAbstractRole"));
var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader"));
var _isInteractiveElement = _interopRequireDefault(require("../util/isInteractiveElement"));
var _isInteractiveRole = _interopRequireDefault(require("../util/isInteractiveRole"));
var _isNonInteractiveElement = _interopRequireDefault(require("../util/isNonInteractiveElement"));
var _isNonInteractiveRole = _interopRequireDefault(require("../util/isNonInteractiveRole"));
var _isNonLiteralProperty = _interopRequireDefault(require("../util/isNonLiteralProperty"));
var _isPresentationRole = _interopRequireDefault(require("../util/isPresentationRole"));
/**
* @fileoverview Enforce static elements have no interactive handlers.
* @author Ethan Cohen
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Static HTML elements with event handlers require a role.';
var domElements = (0, _toConsumableArray2["default"])(_ariaQuery.dom.keys());
var defaultInteractiveProps = [].concat((0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.focus), (0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.keyboard), (0, _toConsumableArray2["default"])(_jsxAstUtils.eventHandlersByType.mouse));
var schema = (0, _schemas.generateObjSchema)({
handlers: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/no-static-element-interactions.md'
},
schema: [schema]
},
create: function create(context) {
var options = context.options;
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
var type = (0, _jsxAstUtils.elementType)(node);
var _ref = options[0] || {},
allowExpressionValues = _ref.allowExpressionValues,
_ref$handlers = _ref.handlers,
handlers = _ref$handlers === void 0 ? defaultInteractiveProps : _ref$handlers;
var hasInteractiveProps = handlers.some(function (prop) {
return (0, _jsxAstUtils.hasProp)(attributes, prop) && (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(attributes, prop)) != null;
});
if (!(0, _arrayIncludes["default"])(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
}
if (!hasInteractiveProps || (0, _isHiddenFromScreenReader["default"])(type, attributes) || (0, _isPresentationRole["default"])(type, attributes)) {
// Presentation is an intentional signal from the author that this
// element is not meant to be perceivable. For example, a click screen
// to close a dialog .
return;
}
if ((0, _isInteractiveElement["default"])(type, attributes) || (0, _isInteractiveRole["default"])(type, attributes) || (0, _isNonInteractiveElement["default"])(type, attributes) || (0, _isNonInteractiveRole["default"])(type, attributes) || (0, _isAbstractRole["default"])(type, attributes)) {
// This rule has no opinion about abstract roles.
return;
}
if (allowExpressionValues === true && (0, _isNonLiteralProperty["default"])(attributes, 'role')) {
// This rule has no opinion about non-literal roles.
return;
} // Visible, non-interactive elements should not have an interactive handler.
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,92 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _isSemanticRoleElement = _interopRequireDefault(require("../util/isSemanticRoleElement"));
/**
* @fileoverview Enforce that elements with ARIA roles must
* have all required attributes for that role.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = function errorMessage(role, requiredProps) {
return "Elements with the ARIA role \"".concat(role, "\" must have the following attributes defined: ").concat(String(requiredProps).toLowerCase());
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/role-has-required-aria-props.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute).toLowerCase();
if (name !== 'role') {
return;
}
var type = (0, _jsxAstUtils.elementType)(attribute.parent);
if (!_ariaQuery.dom.get(type)) {
return;
}
var roleAttrValue = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
var attributes = attribute.parent.attributes; // If value is undefined, then the role attribute will be dropped in the DOM.
// If value is null, then getLiteralAttributeValue is telling us
// that the value isn't in the form of a literal.
if (roleAttrValue === undefined || roleAttrValue === null) {
return;
}
var normalizedValues = String(roleAttrValue).toLowerCase().split(' ');
var validRoles = normalizedValues.filter(function (val) {
return (0, _toConsumableArray2["default"])(_ariaQuery.roles.keys()).indexOf(val) > -1;
}); // Check semantic DOM elements
// For example, <input type="checkbox" role="switch" />
if ((0, _isSemanticRoleElement["default"])(type, attributes)) {
return;
} // Check arbitrary DOM elements
validRoles.forEach(function (role) {
var _roles$get = _ariaQuery.roles.get(role),
requiredPropKeyValues = _roles$get.requiredProps;
var requiredProps = Object.keys(requiredPropKeyValues);
if (requiredProps.length > 0) {
var hasRequiredProps = requiredProps.every(function (prop) {
return (0, _jsxAstUtils.getProp)(attribute.parent.attributes, prop);
});
if (hasRequiredProps === false) {
context.report({
node: attribute,
message: errorMessage(role.toLowerCase(), requiredProps)
});
}
}
});
}
};
}
};

View file

@ -0,0 +1,79 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
var _getImplicitRole = _interopRequireDefault(require("../util/getImplicitRole"));
/**
* @fileoverview Enforce that elements with explicit or implicit roles defined contain only
* `aria-*` properties supported by that `role`.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = function errorMessage(attr, role, tag, isImplicit) {
if (isImplicit) {
return "The attribute ".concat(attr, " is not supported by the role ").concat(role, ". This role is implicit on the element ").concat(tag, ".");
}
return "The attribute ".concat(attr, " is not supported by the role ").concat(role, ".");
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/role-supports-aria-props.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
// If role is not explicitly defined, then try and get its implicit role.
var type = (0, _jsxAstUtils.elementType)(node);
var role = (0, _jsxAstUtils.getProp)(node.attributes, 'role');
var roleValue = role ? (0, _jsxAstUtils.getLiteralPropValue)(role) : (0, _getImplicitRole["default"])(type, node.attributes);
var isImplicit = roleValue && role === undefined; // If there is no explicit or implicit role, then assume that the element
// can handle the global set of aria-* properties.
// This actually isn't true - should fix in future release.
if (typeof roleValue !== 'string' || _ariaQuery.roles.get(roleValue) === undefined) {
return;
} // Make sure it has no aria-* properties defined outside of its property set.
var _roles$get = _ariaQuery.roles.get(roleValue),
propKeyValues = _roles$get.props;
var propertySet = Object.keys(propKeyValues);
var invalidAriaPropsForRole = (0, _toConsumableArray2["default"])(_ariaQuery.aria.keys()).filter(function (attribute) {
return propertySet.indexOf(attribute) === -1;
});
node.attributes.forEach(function (prop) {
// Ignore the attribute if its value is null or undefined.
if ((0, _jsxAstUtils.getPropValue)(prop) == null) return; // Ignore the attribute if it's a spread.
if (prop.type === 'JSXSpreadAttribute') return;
var name = (0, _jsxAstUtils.propName)(prop);
if (invalidAriaPropsForRole.indexOf(name) > -1) {
context.report({
node,
message: errorMessage(name, roleValue, type, isImplicit)
});
}
});
}
};
}
};

View file

@ -0,0 +1,53 @@
"use strict";
var _ariaQuery = require("aria-query");
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce scope prop is only used on <th> elements.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'The scope prop can only be used on <th> elements.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/scope.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(node) {
var name = (0, _jsxAstUtils.propName)(node);
if (name && name.toUpperCase() !== 'SCOPE') {
return;
}
var parent = node.parent;
var tagName = (0, _jsxAstUtils.elementType)(parent); // Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (!_ariaQuery.dom.has(tagName)) {
return;
}
if (tagName && tagName.toUpperCase() === 'TH') {
return;
}
context.report({
node,
message: errorMessage
});
}
};
}
};

View file

@ -0,0 +1,46 @@
"use strict";
var _jsxAstUtils = require("jsx-ast-utils");
var _schemas = require("../util/schemas");
/**
* @fileoverview Enforce tabIndex value is not greater than zero.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Avoid positive integer values for tabIndex.';
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {
url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/tabindex-no-positive.md'
},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute).toUpperCase(); // Check if tabIndex is the attribute
if (name !== 'TABINDEX') {
return;
} // Only check literals because we can't infer values from certain expressions.
var value = Number((0, _jsxAstUtils.getLiteralPropValue)(attribute)); // eslint-disable-next-line no-restricted-globals
if (isNaN(value) || value <= 0) {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
}
};