Removed GopherJS, basic frontend completed, need backend changes for

torrent storage
This commit is contained in:
2017-11-30 18:12:11 -05:00
parent 67fdef16b1
commit e98ad2cc88
69321 changed files with 5498914 additions and 337 deletions

View File

@@ -0,0 +1,54 @@
'use strict';
var _emojiRegex = require('emoji-regex');
var _emojiRegex2 = _interopRequireDefault(_emojiRegex);
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var errorMessage = 'Emojis should be wrapped in <span>, have role="img", and have an accessible description with aria-label or aria-labelledby.'; /**
* @fileoverview Enforce emojis are wrapped in <span> and provide screenreader access.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var literalChildValue = node.parent.children.find(function (child) {
return child.type === 'Literal';
});
if (literalChildValue && (0, _emojiRegex2.default)().test(literalChildValue.value)) {
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: node,
message: errorMessage
});
}
}
}
};
}
};

View File

@@ -0,0 +1,209 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _hasAccessibleChild = require('../util/hasAccessibleChild');
var _hasAccessibleChild2 = _interopRequireDefault(_hasAccessibleChild);
var _isPresentationRole = require('../util/isPresentationRole');
var _isPresentationRole2 = _interopRequireDefault(_isPresentationRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 ruleByElement = {
img: function 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, _isPresentationRole2.default)(nodeType, node.attributes)) {
context.report({
node: 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;
}
context.report({
node: node,
message: 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: node,
message: 'Invalid alt value for ' + nodeType + '. Use alt="" for presentational images.'
});
},
object: function object(context, node) {
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 titleProp = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'title'));
var hasTitleAttr = !!titleProp;
if (hasLabel || hasTitleAttr || (0, _hasAccessibleChild2.default)(node.parent)) {
return;
}
context.report({
node: node,
message: 'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby props.'
});
},
area: function area(context, node) {
var ariaLabelPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-label'));
var arialLabelledByPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby'));
var hasLabel = ariaLabelPropValue !== undefined || arialLabelledByPropValue !== undefined;
if (hasLabel) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
if (altProp === undefined) {
context.report({
node: 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: 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 ariaLabelPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-label'));
var arialLabelledByPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby'));
var hasLabel = ariaLabelPropValue !== undefined || arialLabelledByPropValue !== undefined;
if (hasLabel) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
if (altProp === undefined) {
context.report({
node: 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: 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: {},
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(_toConsumableArray(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 _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _hasAccessibleChild = require('../util/hasAccessibleChild');
var _hasAccessibleChild2 = _interopRequireDefault(_hasAccessibleChild);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var errorMessage = 'Anchors must have content and the content must be accessible by a screen reader.'; /**
* @fileoverview Enforce anchor elements to contain accessible content.
* @author Lisa Ring & Niklas Holmberg
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)({ components: _schemas.arraySchema });
module.exports = {
meta: {
docs: {},
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;
} else if ((0, _hasAccessibleChild2.default)(node.parent)) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,122 @@
'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.';
var noHrefErrorMessage = 'The href attribute is required on an anchor. ' + 'Provide a valid, navigable address as the href value.';
var invalidHrefErrorMessage = 'The href attribute requires a valid address. ' + 'Provide a valid, navigable address as the href value.';
var schema = (0, _schemas.generateObjSchema)({
components: _schemas.arraySchema,
specialLink: _schemas.arraySchema,
aspects: (0, _schemas.enumArraySchema)(allAspects, 1)
});
module.exports = {
meta: {
docs: {},
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: 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: 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: node,
message: preferButtonErrorMessage
});
} else if (activeAspects.invalidHref) {
context.report({
node: node,
message: invalidHrefErrorMessage
});
}
}
}
};
}
};

View File

@@ -0,0 +1,77 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _getTabIndex = require('../util/getTabIndex');
var _getTabIndex2 = _interopRequireDefault(_getTabIndex);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 be tabbable';
var schema = (0, _schemas.generateObjSchema)();
var domElements = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
module.exports = {
meta: {
docs: {},
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, _getTabIndex2.default)((0, _jsxAstUtils.getProp)(attributes, 'tabIndex'));
// If this is an interactive element, tabIndex must be either left
// unspecified allowing the inherent tabIndex to obtain or it must be
// zero (allowing for positive, even though that is not ideal). It cannot
// be given a negative value.
if ((0, _isInteractiveElement2.default)(type, attributes) && (tabIndex === undefined || tabIndex >= 0)) {
return;
}
if (tabIndex >= 0) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,67 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _getSuggestion = require('../util/getSuggestion');
var _getSuggestion2 = _interopRequireDefault(_getSuggestion);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @fileoverview Enforce all aria-* properties are valid.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var ariaAttributes = [].concat(_toConsumableArray(_ariaQuery.aria.keys()));
var errorMessage = function errorMessage(name) {
var suggestions = (0, _getSuggestion2.default)(name, ariaAttributes);
var message = name + ': This attribute is an invalid ARIA attribute.';
if (suggestions.length > 0) {
return message + ' Did you mean to use ' + suggestions + '?';
}
return message;
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toLowerCase() : '';
// `aria` needs to be prefix of property.
if (normalizedName.indexOf('aria-') !== 0) {
return;
}
var isValid = ariaAttributes.indexOf(normalizedName) > -1;
if (isValid === false) {
context.report({
node: attribute,
message: errorMessage(name)
});
}
}
};
}
};

View File

@@ -0,0 +1,102 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var errorMessage = function errorMessage(name, type, permittedValues) {
switch (type) {
case 'tristate':
return 'The value for ' + name + ' must be a boolean or the string "mixed".';
case 'token':
return 'The value for ' + name + ' must be a single token from the following: ' + permittedValues + '.';
case 'tokenlist':
return 'The value for ' + name + ' must be a list of one or more tokens from the following: ' + permittedValues + '.';
case 'boolean':
case 'string':
case 'integer':
case 'number':
default:
return 'The value for ' + name + ' must be a ' + type + '.';
}
}; /**
* @fileoverview Enforce ARIA state and property values are valid.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var validityCheck = function validityCheck(value, expectedType, permittedValues) {
switch (expectedType) {
case 'boolean':
return typeof value === 'boolean';
case 'string':
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.
return typeof value !== 'boolean' && isNaN(Number(value)) === false;
case 'token':
return permittedValues.indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1;
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: validityCheck,
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toLowerCase() : '';
// Not a valid aria-* state or property.
if (normalizedName.indexOf('aria-') !== 0 || _ariaQuery.aria.get(normalizedName) === undefined) {
return;
}
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
// We only want to check literal prop values, so just pass if it's null.
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 _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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: {},
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);
var normalizedName = name ? name.toUpperCase() : '';
if (normalizedName !== '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 normalizedValues = String(value).toLowerCase().split(' ');
var validRoles = [].concat(_toConsumableArray(_ariaQuery.roles.keys())).filter(function (role) {
return _ariaQuery.roles.get(role).abstract === false;
});
var isValid = normalizedValues.every(function (val) {
return validRoles.indexOf(val) > -1;
});
if (isValid === true) {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,65 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 \'' + invalidProp + '\'.';
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
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 === undefined ? false : _nodeAttrs$reserved;
// If it's not reserved, then it can have aria-* roles, states, and properties
if (isReservedNodeType === false) {
return;
}
var invalidAttributes = [].concat(_toConsumableArray(_ariaQuery.aria.keys())).concat('role');
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
var name = (0, _jsxAstUtils.propName)(prop);
var normalizedName = name ? name.toLowerCase() : '';
if (invalidAttributes.indexOf(normalizedName) > -1) {
context.report({
node: node,
message: errorMessage(name)
});
}
});
}
};
}
};

View File

@@ -0,0 +1,74 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _schemas = require('../util/schemas');
var _isHiddenFromScreenReader = require('../util/isHiddenFromScreenReader');
var _isHiddenFromScreenReader2 = _interopRequireDefault(_isHiddenFromScreenReader);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
module.exports = {
meta: {
docs: {},
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, _arrayIncludes2.default)(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} else if ((0, _isHiddenFromScreenReader2.default)(type, props)) {
return;
} else if ((0, _isInteractiveElement2.default)(type, props)) {
return;
} else if ((0, _jsxAstUtils.hasAnyProp)(props, requiredProps)) {
return;
}
// Visible, non-interactive elements with click handlers require one keyboard event listener.
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,52 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _hasAccessibleChild = require('../util/hasAccessibleChild');
var _hasAccessibleChild2 = _interopRequireDefault(_hasAccessibleChild);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var errorMessage = 'Headings must have content and the content must be accessible by a screen reader.'; /**
* @fileoverview Enforce heading (h1, h2, etc) elements contain accessible content.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
var schema = (0, _schemas.generateObjSchema)({ components: _schemas.arraySchema });
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = headings.concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
// Only check 'h*' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
} else if ((0, _hasAccessibleChild2.default)(node.parent)) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,61 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
/**
* @fileoverview Enforce links may not point to just #.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Links must not point to "#". ' + 'Use a more descriptive href or use a button instead.';
var schema = (0, _schemas.generateObjSchema)({
components: _schemas.arraySchema,
specialLink: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var options = context.options[0] || {};
var componentOptions = options.components || [];
var typesToValidate = ['a'].concat(componentOptions);
var nodeType = (0, _jsxAstUtils.elementType)(node);
// Only check 'a' elements and custom types.
if (typesToValidate.indexOf(nodeType) === -1) {
return;
}
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);
});
values.forEach(function (value) {
if (value === '#') {
context.report({
node: node,
message: errorMessage
});
}
});
}
};
}
};

View File

@@ -0,0 +1,48 @@
'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: {},
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: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,48 @@
'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: {},
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: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,77 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _isHiddenFromScreenReader = require('../util/isHiddenFromScreenReader');
var _isHiddenFromScreenReader2 = _interopRequireDefault(_isHiddenFromScreenReader);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var REDUNDANT_WORDS = ['image', 'photo', 'picture']; /**
* @fileoverview Enforce img alt attribute does not have the word image, picture, or photo.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Redundant alt attribute. Screen-readers already announce ' + '`img` tags as an image. You don\'t 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: {},
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, _isHiddenFromScreenReader2.default)(nodeType, node.attributes) === false;
var _options$words = options.words,
words = _options$words === undefined ? [] : _options$words;
var redundantWords = REDUNDANT_WORDS.concat(words);
if (typeof value === 'string' && isVisible) {
var hasRedundancy = redundantWords.some(function (word) {
return Boolean(value.match(new RegExp('(?!{)\\b' + word + '\\b(?!})', 'i')));
});
if (hasRedundancy === true) {
context.report({
node: node,
message: errorMessage
});
}
}
}
};
}
};

View File

@@ -0,0 +1,111 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _schemas = require('../util/schemas');
var _isHiddenFromScreenReader = require('../util/isHiddenFromScreenReader');
var _isHiddenFromScreenReader2 = _interopRequireDefault(_isHiddenFromScreenReader);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
var _isInteractiveRole = require('../util/isInteractiveRole');
var _isInteractiveRole2 = _interopRequireDefault(_isInteractiveRole);
var _isNonInteractiveElement = require('../util/isNonInteractiveElement');
var _isNonInteractiveElement2 = _interopRequireDefault(_isNonInteractiveElement);
var _isNonInteractiveRole = require('../util/isNonInteractiveRole');
var _isNonInteractiveRole2 = _interopRequireDefault(_isNonInteractiveRole);
var _isPresentationRole = require('../util/isPresentationRole');
var _isPresentationRole2 = _interopRequireDefault(_isPresentationRole);
var _getTabIndex = require('../util/getTabIndex');
var _getTabIndex2 = _interopRequireDefault(_getTabIndex);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @fileoverview Enforce that elements with onClick handlers must be tabbable.
* @author Ethan Cohen
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)({
tabbable: (0, _schemas.enumArraySchema)([].concat(_toConsumableArray(_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, _arrayIncludes2.default)(klasses, 'widget');
});
}))
});
var domElements = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
var interactiveProps = [].concat(_toConsumableArray(_jsxAstUtils.eventHandlersByType.mouse), _toConsumableArray(_jsxAstUtils.eventHandlersByType.keyboard));
module.exports = {
meta: {
docs: {},
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, _getTabIndex2.default)((0, _jsxAstUtils.getProp)(attributes, 'tabIndex')) !== undefined;
if (!(0, _arrayIncludes2.default)(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} else if (!hasInteractiveProps || (0, _isHiddenFromScreenReader2.default)(type, attributes) || (0, _isPresentationRole2.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, _isInteractiveRole2.default)(type, attributes) && !(0, _isInteractiveElement2.default)(type, attributes) && !(0, _isNonInteractiveElement2.default)(type, attributes) && !(0, _isNonInteractiveRole2.default)(type, attributes) && !hasTabindex) {
var role = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(attributes, 'role'));
if ((0, _arrayIncludes2.default)(tabbable, role)) {
// Always tabbable, tabIndex = 0
context.report({
node: node,
message: 'Elements with the \'' + role + '\' interactive role must be tabbable.'
});
} else {
// Focusable, tabIndex = -1 or 0
context.report({
node: node,
message: 'Elements with the \'' + role + '\' interactive role must be focusable.'
});
}
}
}
};
}
};

View File

@@ -0,0 +1,88 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
/**
* @fileoverview Enforce label tags have htmlFor attribute.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var errorMessage = 'Form label must have associated control';
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'])]
}
}
};
var validateNesting = function validateNesting(node) {
return !!node.parent.children.find(function (child) {
return child.type === 'JSXElement';
});
};
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) {
return required === 'nesting' ? validateNesting(node) : validateId(node);
};
var isValid = function isValid(node, required) {
if (Array.isArray(required.some)) {
return required.some.some(function (rule) {
return validate(node, rule);
});
} else if (Array.isArray(required.every)) {
return required.every.every(function (rule) {
return validate(node, rule);
});
}
return validate(node, required);
};
module.exports = {
meta: {
docs: {},
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 || 'id';
if (!isValid(node, required)) {
context.report({
node: node,
message: errorMessage
});
}
}
};
}
};

View File

@@ -0,0 +1,74 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _ISO = require('../util/attributes/ISO.json');
var _ISO2 = _interopRequireDefault(_ISO);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var errorMessage = 'lang attribute must have a valid value.'; /**
* @fileoverview Enforce lang attribute has a valid value.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
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;
} else if (value === undefined) {
context.report({
node: node,
message: errorMessage
});
return;
}
var hyphen = value.indexOf('-');
var lang = hyphen > -1 ? value.substring(0, hyphen) : value;
var country = hyphen > -1 ? value.substring(3) : undefined;
if (_ISO2.default.languages.indexOf(lang) > -1 && (country === undefined || _ISO2.default.countries.indexOf(country) > -1)) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,91 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var errorMessage = 'Media elements such as <audio> and <video> must have a <track> for captions.'; /**
* @fileoverview <audio> and <video> elements must have a <track> for captions.
* @author Ethan Cohen
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
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: {},
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;
}
// $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,66 @@
'use strict';
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: {},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
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 || onMouseOverValue !== undefined)) {
var hasOnFocus = (0, _jsxAstUtils.getProp)(attributes, 'onFocus');
var onFocusValue = (0, _jsxAstUtils.getPropValue)(hasOnFocus);
if (hasOnFocus === false || onFocusValue === null || onFocusValue === undefined) {
context.report({
node: node,
message: mouseOverErrorMessage
});
}
}
// Checkout onmouseout / onblur pairing
var onMouseOut = (0, _jsxAstUtils.getProp)(attributes, 'onMouseOut');
var onMouseOutValue = (0, _jsxAstUtils.getPropValue)(onMouseOut);
if (onMouseOut && (onMouseOutValue !== null || onMouseOutValue !== undefined)) {
var hasOnBlur = (0, _jsxAstUtils.getProp)(attributes, 'onBlur');
var onBlurValue = (0, _jsxAstUtils.getPropValue)(hasOnBlur);
if (hasOnBlur === false || onBlurValue === null || onBlurValue === undefined) {
context.report({
node: node,
message: mouseOutErrorMessage
});
}
}
}
};
}
};

View File

@@ -0,0 +1,41 @@
'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: {},
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: 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');
var errorMessage = 'The autoFocus prop should not be used, as it can reduce usability and accessibility for users.'; /**
* @fileoverview Enforce autoFocus prop is not used.
* @author Ethan Cohen <@evcohen>
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)({
ignoreNonDOM: {
type: 'boolean',
default: false
}
});
module.exports = {
meta: {
docs: {},
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,51 @@
'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 <' + 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: {},
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: node,
message: errorMessage(distractingElement)
});
}
}
};
}
};

View File

@@ -0,0 +1,89 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
var _isNonInteractiveRole = require('../util/isNonInteractiveRole');
var _isNonInteractiveRole2 = _interopRequireDefault(_isNonInteractiveRole);
var _isPresentationRole = require('../util/isPresentationRole');
var _isPresentationRole2 = _interopRequireDefault(_isPresentationRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
module.exports = {
meta: {
docs: {},
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);
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, _arrayIncludes2.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 (Object.prototype.hasOwnProperty.call(allowedRoles, type) && (0, _arrayIncludes2.default)(allowedRoles[type], role)) {
return;
}
if ((0, _isInteractiveElement2.default)(type, attributes) && ((0, _isNonInteractiveRole2.default)(type, attributes) || (0, _isPresentationRole2.default)(type, attributes))) {
// Visible, non-interactive elements should not have an interactive handler.
context.report({
node: attribute,
message: errorMessage
});
}
}
};
}
};

View File

@@ -0,0 +1,100 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _schemas = require('../util/schemas');
var _isAbstractRole = require('../util/isAbstractRole');
var _isAbstractRole2 = _interopRequireDefault(_isAbstractRole);
var _isHiddenFromScreenReader = require('../util/isHiddenFromScreenReader');
var _isHiddenFromScreenReader2 = _interopRequireDefault(_isHiddenFromScreenReader);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
var _isInteractiveRole = require('../util/isInteractiveRole');
var _isInteractiveRole2 = _interopRequireDefault(_isInteractiveRole);
var _isNonInteractiveElement = require('../util/isNonInteractiveElement');
var _isNonInteractiveElement2 = _interopRequireDefault(_isNonInteractiveElement);
var _isNonInteractiveRole = require('../util/isNonInteractiveRole');
var _isNonInteractiveRole2 = _interopRequireDefault(_isNonInteractiveRole);
var _isPresentationRole = require('../util/isPresentationRole');
var _isPresentationRole2 = _interopRequireDefault(_isPresentationRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
var defaultInteractiveProps = _jsxAstUtils.eventHandlers;
var schema = (0, _schemas.generateObjSchema)({
handlers: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {},
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 interactiveProps = options[0] ? options[0].handlers : defaultInteractiveProps;
var hasInteractiveProps = interactiveProps.some(function (prop) {
return (0, _jsxAstUtils.hasProp)(attributes, prop) && (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(attributes, prop)) != null;
});
if (!(0, _arrayIncludes2.default)(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} else if (!hasInteractiveProps || (0, _isHiddenFromScreenReader2.default)(type, attributes) || (0, _isPresentationRole2.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;
} else if ((0, _isInteractiveElement2.default)(type, attributes) || (0, _isInteractiveRole2.default)(type, attributes) || !(0, _isNonInteractiveElement2.default)(type, attributes) && !(0, _isNonInteractiveRole2.default)(type, attributes) || (0, _isAbstractRole2.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: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,84 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _isNonInteractiveElement = require('../util/isNonInteractiveElement');
var _isNonInteractiveElement2 = _interopRequireDefault(_isNonInteractiveElement);
var _isInteractiveRole = require('../util/isInteractiveRole');
var _isInteractiveRole2 = _interopRequireDefault(_isInteractiveRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
module.exports = {
meta: {
docs: {},
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);
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, _arrayIncludes2.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 (Object.prototype.hasOwnProperty.call(allowedRoles, type) && (0, _arrayIncludes2.default)(allowedRoles[type], role)) {
return;
}
if ((0, _isNonInteractiveElement2.default)(type, attributes) && (0, _isInteractiveRole2.default)(type, attributes)) {
context.report({
node: attribute,
message: errorMessage
});
}
}
};
}
};

View File

@@ -0,0 +1,94 @@
'use strict';
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /**
* @fileoverview Disallow tabindex on static and noninteractive elements
* @author jessebeach
*
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
var _isInteractiveRole = require('../util/isInteractiveRole');
var _isInteractiveRole2 = _interopRequireDefault(_isInteractiveRole);
var _schemas = require('../util/schemas');
var _getTabIndex = require('../util/getTabIndex');
var _getTabIndex2 = _interopRequireDefault(_getTabIndex);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var errorMessage = '`tabIndex` should only be declared on interactive elements.';
var schema = (0, _schemas.generateObjSchema)({
roles: _extends({}, _schemas.arraySchema, {
description: 'An array of ARIA roles'
}),
tags: _extends({}, _schemas.arraySchema, {
description: 'An array of HTML tag names'
})
});
module.exports = {
meta: {
docs: {},
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, _getTabIndex2.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;
if (tags && (0, _arrayIncludes2.default)(tags, type) || roles && (0, _arrayIncludes2.default)(roles, role)) {
return;
}
if ((0, _isInteractiveElement2.default)(type, attributes) || (0, _isInteractiveRole2.default)(type, attributes)) {
return;
}
if (tabIndex >= 0) {
context.report({
node: tabIndexProp,
message: errorMessage
});
}
}
};
}
};

View File

@@ -0,0 +1,49 @@
'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: {},
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: node,
message: errorMessage
});
}
}
};
}
};

View File

@@ -0,0 +1,55 @@
'use strict';
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _getImplicitRole = require('../util/getImplicitRole');
var _getImplicitRole2 = _interopRequireDefault(_getImplicitRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var errorMessage = function errorMessage(element, implicitRole) {
return 'The element ' + element + ' has an implicit role of ' + implicitRole + '. Defining this explicitly is redundant and should be avoided.';
}; /**
* @fileoverview Enforce explicit role property is not the
* same as implicit/default role property on element.
* @author Ethan Cohen <@evcohen>
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
var implicitRole = (0, _getImplicitRole2.default)(type, node.attributes);
if (implicitRole === '') {
return;
}
var role = (0, _jsxAstUtils.getProp)(node.attributes, 'role');
var roleValue = (0, _jsxAstUtils.getLiteralPropValue)(role);
if (typeof roleValue === 'string' && roleValue.toUpperCase() === implicitRole.toUpperCase()) {
context.report({
node: node,
message: errorMessage(type, implicitRole.toLowerCase())
});
}
}
};
}
};

View File

@@ -0,0 +1,101 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _arrayIncludes = require('array-includes');
var _arrayIncludes2 = _interopRequireDefault(_arrayIncludes);
var _schemas = require('../util/schemas');
var _isAbstractRole = require('../util/isAbstractRole');
var _isAbstractRole2 = _interopRequireDefault(_isAbstractRole);
var _isHiddenFromScreenReader = require('../util/isHiddenFromScreenReader');
var _isHiddenFromScreenReader2 = _interopRequireDefault(_isHiddenFromScreenReader);
var _isInteractiveElement = require('../util/isInteractiveElement');
var _isInteractiveElement2 = _interopRequireDefault(_isInteractiveElement);
var _isInteractiveRole = require('../util/isInteractiveRole');
var _isInteractiveRole2 = _interopRequireDefault(_isInteractiveRole);
var _isNonInteractiveElement = require('../util/isNonInteractiveElement');
var _isNonInteractiveElement2 = _interopRequireDefault(_isNonInteractiveElement);
var _isNonInteractiveRole = require('../util/isNonInteractiveRole');
var _isNonInteractiveRole2 = _interopRequireDefault(_isNonInteractiveRole);
var _isPresentationRole = require('../util/isPresentationRole');
var _isPresentationRole2 = _interopRequireDefault(_isPresentationRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 = [].concat(_toConsumableArray(_ariaQuery.dom.keys()));
var defaultInteractiveProps = _jsxAstUtils.eventHandlers;
var schema = (0, _schemas.generateObjSchema)({
handlers: _schemas.arraySchema
});
module.exports = {
meta: {
docs: {},
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 interactiveProps = options[0] ? options[0].handlers : defaultInteractiveProps;
var hasInteractiveProps = interactiveProps.some(function (prop) {
return (0, _jsxAstUtils.hasProp)(attributes, prop) && (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(attributes, prop)) != null;
});
if (!(0, _arrayIncludes2.default)(domElements, type)) {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
return;
} else if (!hasInteractiveProps || (0, _isHiddenFromScreenReader2.default)(type, attributes) || (0, _isPresentationRole2.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;
} else if ((0, _isInteractiveElement2.default)(type, attributes) || (0, _isInteractiveRole2.default)(type, attributes) || (0, _isNonInteractiveElement2.default)(type, attributes) || (0, _isNonInteractiveRole2.default)(type, attributes) || (0, _isAbstractRole2.default)(type, attributes)) {
// This rule has no opinion about abstract roles.
return;
}
// Visible, non-interactive elements should not have an interactive handler.
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,76 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 "' + role + '" must have the following ' + ('attributes defined: ' + String(requiredProps).toLowerCase());
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toLowerCase() : '';
if (normalizedName !== '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 normalizedValues = String(value).toLowerCase().split(' ');
var validRoles = normalizedValues.filter(function (val) {
return [].concat(_toConsumableArray(_ariaQuery.roles.keys())).indexOf(val) > -1;
});
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,83 @@
'use strict';
var _ariaQuery = require('aria-query');
var _jsxAstUtils = require('jsx-ast-utils');
var _schemas = require('../util/schemas');
var _getImplicitRole = require('../util/getImplicitRole');
var _getImplicitRole2 = _interopRequireDefault(_getImplicitRole);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* @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 ' + attr + ' is not supported by the role ' + role + '. This role is implicit on the element ' + tag + '.';
}
return 'The attribute ' + attr + ' is not supported by the role ' + role + '.';
};
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
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, _getImplicitRole2.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 = [].concat(_toConsumableArray(_ariaQuery.aria.keys())).filter(function (attribute) {
return propertySet.indexOf(attribute) === -1;
});
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
var name = (0, _jsxAstUtils.propName)(prop);
if (invalidAriaPropsForRole.indexOf(name) > -1) {
context.report({
node: 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');
var errorMessage = 'The scope prop can only be used on <th> elements.'; /**
* @fileoverview Enforce scope prop is only used on <th> elements.
* @author Ethan Cohen
*/
// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------
var schema = (0, _schemas.generateObjSchema)();
module.exports = {
meta: {
docs: {},
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;
} else if (tagName && tagName.toUpperCase() === 'TH') {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

View File

@@ -0,0 +1,51 @@
'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: {},
schema: [schema]
},
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
// Check if tabIndex is the attribute
if (normalizedName !== 'TABINDEX') {
return;
}
// Only check literals because we can't infer values from certain expressions.
var value = Number((0, _jsxAstUtils.getLiteralPropValue)(attribute));
if (isNaN(value) || value <= 0) {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
}
};