Socket
Socket
Sign inDemoInstall

eslint-plugin-jsx-a11y

Package Overview
Dependencies
154
Maintainers
2
Versions
81
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.1 to 2.1.0

docs/rules/anchor-has-content.md

8

CHANGELOG.md

@@ -0,1 +1,9 @@

2.1.0 / 2016-08-10
==================
- [fix] Require `aria-checked` for roles that are subclasses of `checkbox`
- [new] Add `anchor-has-content` rule.
- [refactor] Use new eslint rule syntax
- [new] Add support for custom words in `img-redundant-alt` (mainly for i18n).
2.0.1 / 2016-07-13

@@ -2,0 +10,0 @@ ==================

14

docs/rules/img-redundant-alt.md

@@ -6,5 +6,17 @@ # img-redundant-alt

## Rule details
This rule takes one optional argument of type string or array of strings. These strings can be used to specify custom words that should be checked for in the alt prop. Useful for specifying words in other languages.
This rule takes no arguments. This rule will first check if `aria-hidden` is true to determine whether to enforce the rule. If the image is hidden, then rule will always succeed.
The rule will first check if `aria-hidden` is true to determine whether to enforce the rule. If the image is hidden, then rule will always succeed.
To tell this plugin to also check for custom words , specify this in your `.eslintrc` file:
```json
{
"rules": {
"jsx-a11y/img-redundant-alt": [ 2, "Bild" ], // OR
"jsx-a11y/img-redundant-alt": [ 2, [ "Bild", "Foto" ] ]
}
}
```
### Succeed

@@ -11,0 +23,0 @@ ```jsx

@@ -7,2 +7,3 @@ 'use strict';

rules: {
'anchor-has-content': require('./rules/anchor-has-content'),
'aria-props': require('./rules/aria-props'),

@@ -38,2 +39,3 @@ 'aria-proptypes': require('./rules/aria-proptypes'),

rules: {
'jsx-a11y/anchor-has-content': 2,
'jsx-a11y/aria-props': 2,

@@ -40,0 +42,0 @@ 'jsx-a11y/aria-proptypes': 2,

46

lib/rules/aria-props.js

@@ -36,25 +36,31 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
// `aria` needs to be prefix of property.
if (normalizedName.indexOf('ARIA-') !== 0) {
return;
}
schema: [{ type: 'object' }]
},
var isValid = Object.keys(_ARIA2.default).indexOf(normalizedName) > -1;
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
if (isValid === false) {
context.report({
node: attribute,
message: errorMessage(name)
});
// `aria` needs to be prefix of property.
if (normalizedName.indexOf('ARIA-') !== 0) {
return;
}
var isValid = Object.keys(_ARIA2.default).indexOf(normalizedName) > -1;
if (isValid === false) {
context.report({
node: attribute,
message: errorMessage(name)
});
}
}
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -60,40 +60,46 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
// Not a valid aria-* state or property.
if (normalizedName.indexOf('ARIA-') !== 0 || _ARIA2.default[normalizedName] === undefined) {
return;
}
schema: [{ type: 'object' }]
},
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
// We only want to check literal prop values, so just pass if it's null.
if (value === null) {
return;
}
// Not a valid aria-* state or property.
if (normalizedName.indexOf('ARIA-') !== 0 || _ARIA2.default[normalizedName] === undefined) {
return;
}
// These are the attributes of the property/state to check against.
var attributes = _ARIA2.default[normalizedName];
var permittedType = attributes.type;
var allowUndefined = attributes.allowUndefined || false;
var permittedValues = attributes.values || [];
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
var isValid = validityCheck(value, permittedType, permittedValues) || allowUndefined && value === undefined;
// We only want to check literal prop values, so just pass if it's null.
if (value === null) {
return;
}
if (isValid) {
return;
}
// These are the attributes of the property/state to check against.
var attributes = _ARIA2.default[normalizedName];
var permittedType = attributes.type;
var allowUndefined = attributes.allowUndefined || false;
var permittedValues = attributes.values || [];
context.report({
node: attribute,
message: errorMessage(name, permittedType, permittedValues)
});
}
};
};
var isValid = validityCheck(value, permittedType, permittedValues) || allowUndefined && value === undefined;
module.exports.schema = [{ type: 'object' }];
if (isValid) {
return;
}
context.report({
node: attribute,
message: errorMessage(name, permittedType, permittedValues)
});
}
};
}
};

@@ -22,41 +22,47 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
if (normalizedName !== 'ROLE') {
return;
}
schema: [{ type: 'object' }]
},
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
// 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;
}
if (normalizedName !== 'ROLE') {
return;
}
var normalizedValues = String(value).toUpperCase().split(' ');
var validRoles = Object.keys(_role2.default).filter(function (role) {
return _role2.default[role].abstract === false;
});
var isValid = normalizedValues.every(function (val) {
return validRoles.indexOf(val) > -1;
});
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
if (isValid === true) {
return;
}
// 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;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
};
var normalizedValues = String(value).toUpperCase().split(' ');
var validRoles = Object.keys(_role2.default).filter(function (role) {
return _role2.default[role].abstract === false;
});
var isValid = normalizedValues.every(function (val) {
return validRoles.indexOf(val) > -1;
});
module.exports.schema = [{ type: 'object' }];
if (isValid === true) {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
}
};

@@ -27,35 +27,41 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
var nodeAttrs = _DOM2.default[nodeType];
var isReservedNodeType = nodeAttrs && nodeAttrs.reserved || false;
module.exports = {
meta: {
docs: {},
// If it's not reserved, then it can have ARIA-* roles, states, and properties
if (isReservedNodeType === false) {
return;
}
schema: [{ type: 'object' }]
},
var invalidAttributes = Object.keys(_ARIA2.default).concat('ROLE');
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
var nodeAttrs = _DOM2.default[nodeType];
var isReservedNodeType = nodeAttrs && nodeAttrs.reserved || false;
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
// If it's not reserved, then it can have ARIA-* roles, states, and properties
if (isReservedNodeType === false) {
return;
}
var name = (0, _jsxAstUtils.propName)(prop);
var normalizedName = name ? name.toUpperCase() : '';
var invalidAttributes = Object.keys(_ARIA2.default).concat('ROLE');
if (invalidAttributes.indexOf(normalizedName) > -1) {
context.report({
node: node,
message: errorMessage(name)
});
}
});
}
};
};
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
module.exports.schema = [{ type: 'object' }];
var name = (0, _jsxAstUtils.propName)(prop);
var normalizedName = name ? name.toUpperCase() : '';
if (invalidAttributes.indexOf(normalizedName) > -1) {
context.report({
node: node,
message: errorMessage(name)
});
}
});
}
};
}
};

@@ -24,50 +24,56 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = headings.concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
module.exports = {
meta: {
docs: {},
// Only check 'h*' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
schema: [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}]
},
var isAccessible = node.parent.children.some(function (child) {
switch (child.type) {
case 'Literal':
return Boolean(child.value);
case 'JSXElement':
return !(0, _isHiddenFromScreenReader2.default)((0, _jsxAstUtils.elementType)(child.openingElement), child.openingElement.attributes);
case 'JSXExpressionContainer':
if (child.expression.type === 'Identifier') {
return child.expression.name !== 'undefined';
}
return true;
default:
return false;
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;
}
}) || (0, _jsxAstUtils.hasProp)(node.attributes, 'dangerouslySetInnerHTML');
if (isAccessible) {
return;
}
var isAccessible = node.parent.children.some(function (child) {
switch (child.type) {
case 'Literal':
return Boolean(child.value);
case 'JSXElement':
return !(0, _isHiddenFromScreenReader2.default)((0, _jsxAstUtils.elementType)(child.openingElement), child.openingElement.attributes);
case 'JSXExpressionContainer':
if (child.expression.type === 'Identifier') {
return child.expression.name !== 'undefined';
}
return true;
default:
return false;
}
}) || (0, _jsxAstUtils.hasProp)(node.attributes, 'dangerouslySetInnerHTML');
context.report({
node: node,
message: errorMessage
});
}
};
};
if (isAccessible) {
return;
}
module.exports.schema = [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}];
context.report({
node: node,
message: errorMessage
});
}
};
}
};

@@ -14,35 +14,41 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = ['a'].concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
module.exports = {
meta: {
docs: {},
// Only check 'a' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
schema: [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}]
},
var href = (0, _jsxAstUtils.getProp)(node.attributes, 'href');
var value = (0, _jsxAstUtils.getPropValue)(href);
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = ['a'].concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (href && value === '#') {
context.report({
node: node,
message: errorMessage
});
// Only check 'a' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
var href = (0, _jsxAstUtils.getProp)(node.attributes, 'href');
var value = (0, _jsxAstUtils.getPropValue)(href);
if (href && value === '#') {
context.report({
node: node,
message: errorMessage
});
}
}
}
};
};
module.exports.schema = [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}];
};
}
};

@@ -14,25 +14,31 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
module.exports = {
meta: {
docs: {},
if (type && type !== 'html') {
return;
}
schema: [{ type: 'object' }]
},
var lang = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'lang'));
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
if (lang) {
return;
}
if (type && type !== 'html') {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
};
var lang = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'lang'));
module.exports.schema = [{ type: 'object' }];
if (lang) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

@@ -5,47 +5,64 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = ['img'].concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
module.exports = {
meta: {
docs: {},
// Only check 'img' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
schema: [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}]
},
var roleProp = (0, _jsxAstUtils.getProp)(node.attributes, 'role');
var roleValue = (0, _jsxAstUtils.getPropValue)(roleProp);
var isPresentation = roleProp && typeof roleValue === 'string' && roleValue.toLowerCase() === 'presentation';
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = ['img'].concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (isPresentation) {
return;
}
// Only check 'img' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
var roleProp = (0, _jsxAstUtils.getProp)(node.attributes, 'role');
var roleValue = (0, _jsxAstUtils.getPropValue)(roleProp);
var isPresentation = roleProp && typeof roleValue === 'string' && roleValue.toLowerCase() === 'presentation';
// Missing alt prop error.
if (altProp === undefined) {
if (isPresentation) {
return;
}
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
// Missing alt prop error.
if (altProp === undefined) {
context.report({
node: node,
message: nodeType + ' elements must have an alt prop or use role="presentation".'
});
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: nodeType + ' elements must have an alt prop or use role="presentation".'
message: 'Invalid alt value for ' + nodeType + '. Use alt="" or role="presentation" for presentational 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="" or role="presentation" for presentational images.'
});
}
};
};
}
}; /**

@@ -58,13 +75,2 @@ * @fileoverview Enforce img tag uses alt attribute.

// Rule Definition
// ----------------------------------------------------------------------------
module.exports.schema = [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}];
// ----------------------------------------------------------------------------

@@ -22,39 +22,61 @@ 'use strict';

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` in the alt prop.';
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.';
module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var type = (0, _jsxAstUtils.elementType)(node);
if (type !== 'img') {
return;
}
module.exports = {
meta: {
docs: {},
var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
// Return if alt prop is not present.
if (altProp === undefined) {
return;
}
schema: [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}]
},
var value = (0, _jsxAstUtils.getLiteralPropValue)(altProp);
var isVisible = (0, _isHiddenFromScreenReader2.default)(type, node.attributes) === false;
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var REDUNDANT_WORDS_EXTENDED = void 0;
if (typeof value === 'string' && isVisible) {
var hasRedundancy = REDUNDANT_WORDS.some(function (word) {
return Boolean(value.match(new RegExp('(?!{)' + word + '(?!})', 'gi')));
});
if (context.options[0]) {
REDUNDANT_WORDS_EXTENDED = REDUNDANT_WORDS.concat(context.options[0]);
} else {
REDUNDANT_WORDS_EXTENDED = REDUNDANT_WORDS;
}
var type = (0, _jsxAstUtils.elementType)(node);
if (type !== 'img') {
return;
}
if (hasRedundancy === true) {
context.report({
node: node,
message: errorMessage
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)(type, node.attributes) === false;
if (typeof value === 'string' && isVisible) {
var hasRedundancy = REDUNDANT_WORDS_EXTENDED.some(function (word) {
return Boolean(value.match(new RegExp('(?!{)' + word + '(?!})', 'gi')));
});
if (hasRedundancy === true) {
context.report({
node: node,
message: errorMessage
});
}
return;
}
return;
}
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -14,36 +14,42 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = ['label'].concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
module.exports = {
meta: {
docs: {},
// Only check 'label' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
schema: [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}]
},
var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
var isInvalid = htmlForAttr === false || !htmlForValue;
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var typeCheck = ['label'].concat(context.options[0]);
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (isInvalid) {
context.report({
node: node,
message: errorMessage
});
// Only check 'label' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
var isInvalid = htmlForAttr === false || !htmlForValue;
if (isInvalid) {
context.report({
node: node,
message: errorMessage
});
}
}
}
};
};
module.exports.schema = [{
oneOf: [{ type: 'string' }, {
type: 'array',
items: {
type: 'string'
},
minItems: 1,
uniqueItems: true
}]
}];
};
}
};

@@ -22,23 +22,46 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(node) {
var name = (0, _jsxAstUtils.propName)(node);
if (name && name.toUpperCase() !== 'LANG') {
return;
}
module.exports = {
meta: {
docs: {},
var parent = node.parent;
schema: [{ type: 'object' }]
},
var type = (0, _jsxAstUtils.elementType)(parent);
if (type && type !== 'html') {
return;
}
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(node) {
var name = (0, _jsxAstUtils.propName)(node);
if (name && name.toUpperCase() !== 'LANG') {
return;
}
var value = (0, _jsxAstUtils.getLiteralPropValue)(node);
var parent = node.parent;
// Don't check identifiers
if (value === null) {
return;
} else if (value === undefined) {
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({

@@ -48,22 +71,5 @@ node: node,

});
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
});
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -17,41 +17,47 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
module.exports = {
meta: {
docs: {},
// Check onmouseover / onfocus pairing.
var onMouseOver = (0, _jsxAstUtils.getProp)(attributes, 'onMouseOver');
var onMouseOverValue = (0, _jsxAstUtils.getPropValue)(onMouseOver);
schema: [{ type: 'object' }]
},
if (onMouseOver && (onMouseOverValue !== null || onMouseOverValue !== undefined)) {
var hasOnFocus = (0, _jsxAstUtils.getProp)(attributes, 'onFocus');
var onFocusValue = (0, _jsxAstUtils.getPropValue)(hasOnFocus);
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
if (hasOnFocus === false || onFocusValue === null || onFocusValue === undefined) {
context.report({
node: node,
message: mouseOverErrorMessage
});
// 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);
// 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
});
if (hasOnBlur === false || onBlurValue === null || onBlurValue === undefined) {
context.report({
node: node,
message: mouseOutErrorMessage
});
}
}
}
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -14,18 +14,24 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var accessKey = (0, _jsxAstUtils.getProp)(node.attributes, 'accesskey');
var accessKeyValue = (0, _jsxAstUtils.getPropValue)(accessKey);
module.exports = {
meta: {
docs: {},
if (accessKey && accessKeyValue) {
context.report({
node: node,
message: errorMessage
});
schema: [{ type: 'object' }]
},
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
});
}
}
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -14,17 +14,23 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var isMarquee = (0, _jsxAstUtils.elementType)(node) === 'marquee';
module.exports = {
meta: {
docs: {},
if (isMarquee) {
context.report({
node: node,
message: errorMessage
});
schema: [{ type: 'object' }]
},
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var isMarquee = (0, _jsxAstUtils.elementType)(node) === 'marquee';
if (isMarquee) {
context.report({
node: node,
message: errorMessage
});
}
}
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -16,24 +16,30 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
module.exports = {
meta: {
docs: {},
if (applicableTypes.indexOf(nodeType) === -1) {
return;
}
schema: [{ type: 'object' }]
},
var onChange = (0, _jsxAstUtils.getProp)(node.attributes, 'onChange');
var hasOnBlur = (0, _jsxAstUtils.getProp)(node.attributes, 'onBlur') !== undefined;
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var nodeType = (0, _jsxAstUtils.elementType)(node);
if (onChange && !hasOnBlur) {
context.report({
node: node,
message: errorMessage
});
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
});
}
}
}
};
};
module.exports.schema = [{ type: 'object' }];
};
}
};

@@ -30,29 +30,35 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
module.exports = {
meta: {
docs: {},
if ((0, _jsxAstUtils.getProp)(attributes, 'onClick') === undefined) {
return;
}
schema: [{ type: 'object' }]
},
var type = (0, _jsxAstUtils.elementType)(node);
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
if ((0, _isHiddenFromScreenReader2.default)(type, attributes)) {
return;
} else if ((0, _isInteractiveElement2.default)(type, attributes)) {
return;
} else if ((0, _getTabIndex2.default)((0, _jsxAstUtils.getProp)(attributes, 'tabIndex')) !== undefined) {
return;
}
if ((0, _jsxAstUtils.getProp)(attributes, 'onClick') === undefined) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
};
var type = (0, _jsxAstUtils.elementType)(node);
module.exports.schema = [{ type: 'object' }];
if ((0, _isHiddenFromScreenReader2.default)(type, attributes)) {
return;
} else if ((0, _isInteractiveElement2.default)(type, attributes)) {
return;
} else if ((0, _getTabIndex2.default)((0, _jsxAstUtils.getProp)(attributes, 'tabIndex')) !== undefined) {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

@@ -25,29 +25,35 @@ 'use strict';

module.exports = function (context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
if ((0, _jsxAstUtils.getProp)(attributes, 'onclick') === undefined) {
return;
}
module.exports = {
meta: {
docs: {},
var type = (0, _jsxAstUtils.elementType)(node);
schema: [{ type: 'object' }]
},
if ((0, _isHiddenFromScreenReader2.default)(type, attributes)) {
return;
} else if ((0, _isInteractiveElement2.default)(type, attributes)) {
return;
} else if ((0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(attributes, 'role'))) {
return;
}
create: function create(context) {
return {
JSXOpeningElement: function JSXOpeningElement(node) {
var attributes = node.attributes;
if ((0, _jsxAstUtils.getProp)(attributes, 'onclick') === undefined) {
return;
}
// Visible, non-interactive elements require role attribute.
context.report({
node: node,
message: errorMessage
});
}
};
};
var type = (0, _jsxAstUtils.elementType)(node);
module.exports.schema = [{ type: 'object' }];
if ((0, _isHiddenFromScreenReader2.default)(type, attributes)) {
return;
} else if ((0, _isInteractiveElement2.default)(type, attributes)) {
return;
} else if ((0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(attributes, 'role'))) {
return;
}
// Visible, non-interactive elements require role attribute.
context.report({
node: node,
message: errorMessage
});
}
};
}
};

@@ -25,47 +25,53 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
if (normalizedName !== 'ROLE') {
return;
}
schema: [{ type: 'object' }]
},
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
// 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;
}
if (normalizedName !== 'ROLE') {
return;
}
var normalizedValues = String(value).toUpperCase().split(' ');
var validRoles = normalizedValues.filter(function (val) {
return Object.keys(_role2.default).indexOf(val) > -1;
});
var value = (0, _jsxAstUtils.getLiteralPropValue)(attribute);
validRoles.forEach(function (role) {
var requiredProps = _role2.default[role].requiredProps;
// 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).toUpperCase().split(' ');
var validRoles = normalizedValues.filter(function (val) {
return Object.keys(_role2.default).indexOf(val) > -1;
});
if (requiredProps.length > 0) {
var hasRequiredProps = requiredProps.every(function (prop) {
return (0, _jsxAstUtils.getProp)(attribute.parent.attributes, prop);
});
validRoles.forEach(function (role) {
var requiredProps = _role2.default[role].requiredProps;
if (hasRequiredProps === false) {
context.report({
node: attribute,
message: errorMessage(role.toLowerCase(), requiredProps)
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)
});
}
}
}
});
}
};
};
module.exports.schema = [{ type: 'object' }];
});
}
};
}
};

@@ -37,43 +37,49 @@ 'use strict';

module.exports = function (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;
module.exports = {
meta: {
docs: {},
// 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' || _role2.default[roleValue.toUpperCase()] === undefined) {
return;
}
schema: [{ type: 'object' }]
},
// Make sure it has no aria-* properties defined outside of its property set.
var propertySet = _role2.default[roleValue.toUpperCase()].props;
var invalidAriaPropsForRole = Object.keys(_ARIA2.default).filter(function (attribute) {
return propertySet.indexOf(attribute) === -1;
});
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;
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
// 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' || _role2.default[roleValue.toUpperCase()] === undefined) {
return;
}
var name = (0, _jsxAstUtils.propName)(prop);
var normalizedName = name ? name.toUpperCase() : '';
// Make sure it has no aria-* properties defined outside of its property set.
var propertySet = _role2.default[roleValue.toUpperCase()].props;
var invalidAriaPropsForRole = Object.keys(_ARIA2.default).filter(function (attribute) {
return propertySet.indexOf(attribute) === -1;
});
if (invalidAriaPropsForRole.indexOf(normalizedName) > -1) {
context.report({
node: node,
message: errorMessage(name, roleValue, type, isImplicit)
});
}
});
}
};
};
node.attributes.forEach(function (prop) {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
module.exports.schema = [{ type: 'object' }];
var name = (0, _jsxAstUtils.propName)(prop);
var normalizedName = name ? name.toUpperCase() : '';
if (invalidAriaPropsForRole.indexOf(normalizedName) > -1) {
context.report({
node: node,
message: errorMessage(name, roleValue, type, isImplicit)
});
}
});
}
};
}
};

@@ -22,30 +22,36 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(node) {
var name = (0, _jsxAstUtils.propName)(node);
if (name && name.toUpperCase() !== 'SCOPE') {
return;
}
module.exports = {
meta: {
docs: {},
var parent = node.parent;
schema: [{ type: 'object' }]
},
var tagName = (0, _jsxAstUtils.elementType)(parent);
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(node) {
var name = (0, _jsxAstUtils.propName)(node);
if (name && name.toUpperCase() !== 'SCOPE') {
return;
}
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (Object.keys(_DOM2.default).indexOf(tagName) === -1) {
return;
} else if (tagName && tagName.toUpperCase() === 'TH') {
return;
}
var parent = node.parent;
context.report({
node: node,
message: errorMessage
});
}
};
};
var tagName = (0, _jsxAstUtils.elementType)(parent);
module.exports.schema = [{ type: 'object' }];
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (Object.keys(_DOM2.default).indexOf(tagName) === -1) {
return;
} else if (tagName && tagName.toUpperCase() === 'TH') {
return;
}
context.report({
node: node,
message: errorMessage
});
}
};
}
};

@@ -14,28 +14,34 @@ 'use strict';

module.exports = function (context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
// Check if tabIndex is the attribute
if (normalizedName !== 'TABINDEX') {
return;
}
schema: [{ type: 'object' }]
},
// Only check literals because we can't infer values from certain expressions.
var value = Number((0, _jsxAstUtils.getLiteralPropValue)(attribute));
create: function create(context) {
return {
JSXAttribute: function JSXAttribute(attribute) {
var name = (0, _jsxAstUtils.propName)(attribute);
var normalizedName = name ? name.toUpperCase() : '';
if (isNaN(value) || value <= 0) {
return;
}
// Check if tabIndex is the attribute
if (normalizedName !== 'TABINDEX') {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
};
// Only check literals because we can't infer values from certain expressions.
var value = Number((0, _jsxAstUtils.getLiteralPropValue)(attribute));
module.exports.schema = [{ type: 'object' }];
if (isNaN(value) || value <= 0) {
return;
}
context.report({
node: attribute,
message: errorMessage
});
}
};
}
};

@@ -1819,3 +1819,5 @@ {

"MENUITEMCHECKBOX": {
"requiredProps": [],
"requiredProps": [
"ARIA-CHECKED"
],
"abstract": false,

@@ -1843,3 +1845,5 @@ "props": [

"MENUITEMRADIO": {
"requiredProps": [],
"requiredProps": [
"ARIA-CHECKED"
],
"abstract": false,

@@ -1990,3 +1994,5 @@ "props": [

"RADIO": {
"requiredProps": [],
"requiredProps": [
"ARIA-CHECKED"
],
"abstract": false,

@@ -1993,0 +1999,0 @@ "props": [

{
"name": "eslint-plugin-jsx-a11y",
"version": "2.0.1",
"version": "2.1.0",
"description": "A static analysis linter of jsx and their accessibility with screen readers.",

@@ -5,0 +5,0 @@ "keywords": [

@@ -73,2 +73,3 @@ <p align="center">

- [anchor-has-content](docs/rules/anchor-has-content.md): Enforce all anchors to contain accessible content.
- [aria-props](docs/rules/aria-props.md): Enforce all `aria-*` props are valid.

@@ -75,0 +76,0 @@ - [aria-proptypes](docs/rules/aria-proptypes.md): Enforce ARIA state and property values are valid.

@@ -5,2 +5,3 @@ /* eslint-disable global-require */

rules: {
'anchor-has-content': require('./rules/anchor-has-content'),
'aria-props': require('./rules/aria-props'),

@@ -36,2 +37,3 @@ 'aria-proptypes': require('./rules/aria-proptypes'),

rules: {
'jsx-a11y/anchor-has-content': 2,
'jsx-a11y/aria-props': 2,

@@ -38,0 +40,0 @@ 'jsx-a11y/aria-proptypes': 2,

@@ -26,25 +26,31 @@ /**

module.exports = context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
// `aria` needs to be prefix of property.
if (normalizedName.indexOf('ARIA-') !== 0) {
return;
}
schema: [
{ type: 'object' },
],
},
const isValid = Object.keys(ariaAttributes).indexOf(normalizedName) > -1;
create: context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
if (isValid === false) {
context.report({
node: attribute,
message: errorMessage(name),
});
}
},
});
// `aria` needs to be prefix of property.
if (normalizedName.indexOf('ARIA-') !== 0) {
return;
}
module.exports.schema = [
{ type: 'object' },
];
const isValid = Object.keys(ariaAttributes).indexOf(normalizedName) > -1;
if (isValid === false) {
context.report({
node: attribute,
message: errorMessage(name),
});
}
},
}),
};

@@ -53,41 +53,47 @@ /**

module.exports = context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
// Not a valid aria-* state or property.
if (normalizedName.indexOf('ARIA-') !== 0 || ariaAttributes[normalizedName] === undefined) {
return;
}
schema: [
{ type: 'object' },
],
},
const value = getLiteralPropValue(attribute);
create: context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
// We only want to check literal prop values, so just pass if it's null.
if (value === null) {
return;
}
// Not a valid aria-* state or property.
if (normalizedName.indexOf('ARIA-') !== 0 || ariaAttributes[normalizedName] === undefined) {
return;
}
// These are the attributes of the property/state to check against.
const attributes = ariaAttributes[normalizedName];
const permittedType = attributes.type;
const allowUndefined = attributes.allowUndefined || false;
const permittedValues = attributes.values || [];
const value = getLiteralPropValue(attribute);
const isValid = validityCheck(value, permittedType, permittedValues) ||
(allowUndefined && value === undefined);
// We only want to check literal prop values, so just pass if it's null.
if (value === null) {
return;
}
if (isValid) {
return;
}
// These are the attributes of the property/state to check against.
const attributes = ariaAttributes[normalizedName];
const permittedType = attributes.type;
const allowUndefined = attributes.allowUndefined || false;
const permittedValues = attributes.values || [];
context.report({
node: attribute,
message: errorMessage(name, permittedType, permittedValues),
});
},
});
const isValid = validityCheck(value, permittedType, permittedValues) ||
(allowUndefined && value === undefined);
module.exports.schema = [
{ type: 'object' },
];
if (isValid) {
return;
}
context.report({
node: attribute,
message: errorMessage(name, permittedType, permittedValues),
});
},
}),
};

@@ -15,37 +15,43 @@ /**

module.exports = context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
if (normalizedName !== 'ROLE') {
return;
}
schema: [
{ type: 'object' },
],
},
const value = getLiteralPropValue(attribute);
create: context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
// 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;
}
if (normalizedName !== 'ROLE') {
return;
}
const normalizedValues = String(value).toUpperCase().split(' ');
const validRoles = Object.keys(roles).filter(role => roles[role].abstract === false);
const isValid = normalizedValues.every(val => validRoles.indexOf(val) > -1);
const value = getLiteralPropValue(attribute);
if (isValid === true) {
return;
}
// 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;
}
context.report({
node: attribute,
message: errorMessage,
});
},
});
const normalizedValues = String(value).toUpperCase().split(' ');
const validRoles = Object.keys(roles).filter(role => roles[role].abstract === false);
const isValid = normalizedValues.every(val => validRoles.indexOf(val) > -1);
module.exports.schema = [
{ type: 'object' },
];
if (isValid === true) {
return;
}
context.report({
node: attribute,
message: errorMessage,
});
},
}),
};

@@ -19,35 +19,41 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const nodeType = elementType(node);
const nodeAttrs = DOM[nodeType];
const isReservedNodeType = nodeAttrs && nodeAttrs.reserved || false;
module.exports = {
meta: {
docs: {},
// If it's not reserved, then it can have ARIA-* roles, states, and properties
if (isReservedNodeType === false) {
return;
}
schema: [
{ type: 'object' },
],
},
const invalidAttributes = Object.keys(ARIA).concat('ROLE');
create: context => ({
JSXOpeningElement: node => {
const nodeType = elementType(node);
const nodeAttrs = DOM[nodeType];
const isReservedNodeType = nodeAttrs && nodeAttrs.reserved || false;
node.attributes.forEach(prop => {
if (prop.type === 'JSXSpreadAttribute') {
// If it's not reserved, then it can have ARIA-* roles, states, and properties
if (isReservedNodeType === false) {
return;
}
const name = propName(prop);
const normalizedName = name ? name.toUpperCase() : '';
const invalidAttributes = Object.keys(ARIA).concat('ROLE');
if (invalidAttributes.indexOf(normalizedName) > -1) {
context.report({
node,
message: errorMessage(name),
});
}
});
},
});
node.attributes.forEach(prop => {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
module.exports.schema = [
{ type: 'object' },
];
const name = propName(prop);
const normalizedName = name ? name.toUpperCase() : '';
if (invalidAttributes.indexOf(normalizedName) > -1) {
context.report({
node,
message: errorMessage(name),
});
}
});
},
}),
};

@@ -25,57 +25,63 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const typeCheck = headings.concat(context.options[0]);
const nodeType = elementType(node);
module.exports = {
meta: {
docs: {},
// Only check 'h*' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
schema: [
{
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
],
},
const isAccessible = node.parent.children.some(child => {
switch (child.type) {
case 'Literal':
return Boolean(child.value);
case 'JSXElement':
return !isHiddenFromScreenReader(
elementType(child.openingElement),
child.openingElement.attributes
);
case 'JSXExpressionContainer':
if (child.expression.type === 'Identifier') {
return child.expression.name !== 'undefined';
}
return true;
default:
return false;
create: context => ({
JSXOpeningElement: node => {
const typeCheck = headings.concat(context.options[0]);
const nodeType = elementType(node);
// Only check 'h*' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
}) || hasProp(node.attributes, 'dangerouslySetInnerHTML');
const isAccessible = node.parent.children.some(child => {
switch (child.type) {
case 'Literal':
return Boolean(child.value);
case 'JSXElement':
return !isHiddenFromScreenReader(
elementType(child.openingElement),
child.openingElement.attributes
);
case 'JSXExpressionContainer':
if (child.expression.type === 'Identifier') {
return child.expression.name !== 'undefined';
}
return true;
default:
return false;
}
}) || hasProp(node.attributes, 'dangerouslySetInnerHTML');
if (isAccessible) {
return;
}
context.report({
node,
message: errorMessage,
});
},
});
if (isAccessible) {
return;
}
module.exports.schema = [
{
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
];
context.report({
node,
message: errorMessage,
});
},
}),
};

@@ -15,38 +15,44 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const typeCheck = ['a'].concat(context.options[0]);
const nodeType = elementType(node);
module.exports = {
meta: {
docs: {},
// Only check 'a' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
const href = getProp(node.attributes, 'href');
const value = getPropValue(href);
if (href && value === '#') {
context.report({
node,
message: errorMessage,
});
}
},
});
module.exports.schema = [
{
oneOf: [
{ type: 'string' },
schema: [
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
],
},
];
create: context => ({
JSXOpeningElement: node => {
const typeCheck = ['a'].concat(context.options[0]);
const nodeType = elementType(node);
// Only check 'a' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
const href = getProp(node.attributes, 'href');
const value = getPropValue(href);
if (href && value === '#') {
context.report({
node,
message: errorMessage,
});
}
},
}),
};

@@ -14,25 +14,31 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const type = elementType(node);
module.exports = {
meta: {
docs: {},
if (type && type !== 'html') {
return;
}
schema: [
{ type: 'object' },
],
},
const lang = getPropValue(getProp(node.attributes, 'lang'));
create: context => ({
JSXOpeningElement: node => {
const type = elementType(node);
if (lang) {
return;
}
if (type && type !== 'html') {
return;
}
context.report({
node,
message: errorMessage,
});
},
});
const lang = getPropValue(getProp(node.attributes, 'lang'));
module.exports.schema = [
{ type: 'object' },
];
if (lang) {
return;
}
context.report({
node,
message: errorMessage,
});
},
}),
};

@@ -12,64 +12,70 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const typeCheck = ['img'].concat(context.options[0]);
const nodeType = elementType(node);
module.exports = {
meta: {
docs: {},
// Only check 'img' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
schema: [
{
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
],
},
const roleProp = getProp(node.attributes, 'role');
const roleValue = getPropValue(roleProp);
const isPresentation = roleProp && typeof roleValue === 'string'
&& roleValue.toLowerCase() === 'presentation';
create: context => ({
JSXOpeningElement: node => {
const typeCheck = ['img'].concat(context.options[0]);
const nodeType = elementType(node);
if (isPresentation) {
return;
}
// Only check 'img' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
const altProp = getProp(node.attributes, 'alt');
const roleProp = getProp(node.attributes, 'role');
const roleValue = getPropValue(roleProp);
const isPresentation = roleProp && typeof roleValue === 'string'
&& roleValue.toLowerCase() === 'presentation';
// Missing alt prop error.
if (altProp === undefined) {
context.report({
node,
message: `${nodeType} elements must have an alt prop or use role="presentation".`,
});
return;
}
if (isPresentation) {
return;
}
// Check if alt prop is undefined.
const altValue = getPropValue(altProp);
const isNullValued = altProp.value === null; // <img alt />
const altProp = getProp(node.attributes, 'alt');
if ((altValue && !isNullValued) || altValue === '') {
return;
}
// Missing alt prop error.
if (altProp === undefined) {
context.report({
node,
message: `${nodeType} elements must have an alt prop or use role="presentation".`,
});
return;
}
// Undefined alt prop error.
context.report({
node,
message:
`Invalid alt value for ${nodeType}. \
// Check if alt prop is undefined.
const altValue = getPropValue(altProp);
const isNullValued = altProp.value === null; // <img alt />
if ((altValue && !isNullValued) || altValue === '') {
return;
}
// Undefined alt prop error.
context.report({
node,
message:
`Invalid alt value for ${nodeType}. \
Use alt="" or role="presentation" for presentational images.`,
});
},
});
module.exports.schema = [
{
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
];
});
},
}),
};

@@ -21,38 +21,63 @@ /**

'`img` tags as an image. You don\'t need to use the words `image`, ' +
'`photo,` or `picture` in the alt prop.';
'`photo,` or `picture` (or any specified custom words) in the alt prop.';
module.exports = context => ({
JSXOpeningElement: node => {
const type = elementType(node);
if (type !== 'img') {
return;
}
module.exports = {
meta: {
docs: {},
const altProp = getProp(node.attributes, 'alt');
// Return if alt prop is not present.
if (altProp === undefined) {
return;
}
schema: [
{
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
],
},
const value = getLiteralPropValue(altProp);
const isVisible = isHiddenFromScreenReader(type, node.attributes) === false;
create: context => ({
JSXOpeningElement: node => {
let REDUNDANT_WORDS_EXTENDED;
if (typeof value === 'string' && isVisible) {
const hasRedundancy = REDUNDANT_WORDS
.some(word => Boolean(value.match(new RegExp(`(?!{)${word}(?!})`, 'gi'))));
if (context.options[0]) {
REDUNDANT_WORDS_EXTENDED = REDUNDANT_WORDS.concat(context.options[0]);
} else {
REDUNDANT_WORDS_EXTENDED = REDUNDANT_WORDS;
}
const type = elementType(node);
if (type !== 'img') {
return;
}
if (hasRedundancy === true) {
context.report({
node,
message: errorMessage,
});
const altProp = getProp(node.attributes, 'alt');
// Return if alt prop is not present.
if (altProp === undefined) {
return;
}
return;
}
},
});
const value = getLiteralPropValue(altProp);
const isVisible = isHiddenFromScreenReader(type, node.attributes) === false;
module.exports.schema = [
{ type: 'object' },
];
if (typeof value === 'string' && isVisible) {
const hasRedundancy = REDUNDANT_WORDS_EXTENDED
.some(word => Boolean(value.match(new RegExp(`(?!{)${word}(?!})`, 'gi'))));
if (hasRedundancy === true) {
context.report({
node,
message: errorMessage,
});
}
return;
}
},
}),
};

@@ -15,39 +15,45 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const typeCheck = ['label'].concat(context.options[0]);
const nodeType = elementType(node);
module.exports = {
meta: {
docs: {},
// Only check 'label' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
const htmlForAttr = getProp(node.attributes, 'htmlFor');
const htmlForValue = getPropValue(htmlForAttr);
const isInvalid = htmlForAttr === false || !htmlForValue;
if (isInvalid) {
context.report({
node,
message: errorMessage,
});
}
},
});
module.exports.schema = [
{
oneOf: [
{ type: 'string' },
schema: [
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
minItems: 1,
uniqueItems: true,
},
],
},
],
},
];
create: context => ({
JSXOpeningElement: node => {
const typeCheck = ['label'].concat(context.options[0]);
const nodeType = elementType(node);
// Only check 'label' elements and custom types.
if (typeCheck.indexOf(nodeType) === -1) {
return;
}
const htmlForAttr = getProp(node.attributes, 'htmlFor');
const htmlForValue = getPropValue(htmlForAttr);
const isInvalid = htmlForAttr === false || !htmlForValue;
if (isInvalid) {
context.report({
node,
message: errorMessage,
});
}
},
}),
};

@@ -16,47 +16,53 @@ /**

module.exports = context => ({
JSXAttribute: node => {
const name = propName(node);
if (name && name.toUpperCase() !== 'LANG') {
return;
}
module.exports = {
meta: {
docs: {},
const { parent } = node;
const type = elementType(parent);
if (type && type !== 'html') {
return;
}
schema: [
{ type: 'object' },
],
},
const value = getLiteralPropValue(node);
create: context => ({
JSXAttribute: node => {
const name = propName(node);
if (name && name.toUpperCase() !== 'LANG') {
return;
}
// Don't check identifiers
if (value === null) {
return;
} else if (value === undefined) {
context.report({
node,
message: errorMessage,
});
const { parent } = node;
const type = elementType(parent);
if (type && type !== 'html') {
return;
}
return;
}
const value = getLiteralPropValue(node);
const hyphen = value.indexOf('-');
const lang = hyphen > -1 ? value.substring(0, hyphen) : value;
const country = hyphen > -1 ? value.substring(3) : undefined;
// Don't check identifiers
if (value === null) {
return;
} else if (value === undefined) {
context.report({
node,
message: errorMessage,
});
if (ISO_CODES.languages.indexOf(lang) > -1
&& (country === undefined || ISO_CODES.countries.indexOf(country) > -1)) {
return;
}
return;
}
context.report({
node,
message: errorMessage,
});
},
});
const hyphen = value.indexOf('-');
const lang = hyphen > -1 ? value.substring(0, hyphen) : value;
const country = hyphen > -1 ? value.substring(3) : undefined;
module.exports.schema = [
{ type: 'object' },
];
if (ISO_CODES.languages.indexOf(lang) > -1
&& (country === undefined || ISO_CODES.countries.indexOf(country) > -1)) {
return;
}
context.report({
node,
message: errorMessage,
});
},
}),
};

@@ -17,41 +17,47 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const attributes = node.attributes;
module.exports = {
meta: {
docs: {},
// Check onmouseover / onfocus pairing.
const onMouseOver = getProp(attributes, 'onMouseOver');
const onMouseOverValue = getPropValue(onMouseOver);
schema: [
{ type: 'object' },
],
},
if (onMouseOver && (onMouseOverValue !== null || onMouseOverValue !== undefined)) {
const hasOnFocus = getProp(attributes, 'onFocus');
const onFocusValue = getPropValue(hasOnFocus);
create: context => ({
JSXOpeningElement: node => {
const attributes = node.attributes;
if (hasOnFocus === false || onFocusValue === null || onFocusValue === undefined) {
context.report({
node,
message: mouseOverErrorMessage,
});
// Check onmouseover / onfocus pairing.
const onMouseOver = getProp(attributes, 'onMouseOver');
const onMouseOverValue = getPropValue(onMouseOver);
if (onMouseOver && (onMouseOverValue !== null || onMouseOverValue !== undefined)) {
const hasOnFocus = getProp(attributes, 'onFocus');
const onFocusValue = getPropValue(hasOnFocus);
if (hasOnFocus === false || onFocusValue === null || onFocusValue === undefined) {
context.report({
node,
message: mouseOverErrorMessage,
});
}
}
}
// Checkout onmouseout / onblur pairing
const onMouseOut = getProp(attributes, 'onMouseOut');
const onMouseOutValue = getPropValue(onMouseOut);
if (onMouseOut && (onMouseOutValue !== null || onMouseOutValue !== undefined)) {
const hasOnBlur = getProp(attributes, 'onBlur');
const onBlurValue = getPropValue(hasOnBlur);
// Checkout onmouseout / onblur pairing
const onMouseOut = getProp(attributes, 'onMouseOut');
const onMouseOutValue = getPropValue(onMouseOut);
if (onMouseOut && (onMouseOutValue !== null || onMouseOutValue !== undefined)) {
const hasOnBlur = getProp(attributes, 'onBlur');
const onBlurValue = getPropValue(hasOnBlur);
if (hasOnBlur === false || onBlurValue === null || onBlurValue === undefined) {
context.report({
node,
message: mouseOutErrorMessage,
});
if (hasOnBlur === false || onBlurValue === null || onBlurValue === undefined) {
context.report({
node,
message: mouseOutErrorMessage,
});
}
}
}
},
});
module.exports.schema = [
{ type: 'object' },
];
},
}),
};

@@ -16,18 +16,24 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const accessKey = getProp(node.attributes, 'accesskey');
const accessKeyValue = getPropValue(accessKey);
module.exports = {
meta: {
docs: {},
if (accessKey && accessKeyValue) {
context.report({
node,
message: errorMessage,
});
}
schema: [
{ type: 'object' },
],
},
});
module.exports.schema = [
{ type: 'object' },
];
create: context => ({
JSXOpeningElement: node => {
const accessKey = getProp(node.attributes, 'accesskey');
const accessKeyValue = getPropValue(accessKey);
if (accessKey && accessKeyValue) {
context.report({
node,
message: errorMessage,
});
}
},
}),
};

@@ -15,17 +15,23 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const isMarquee = elementType(node) === 'marquee';
module.exports = {
meta: {
docs: {},
if (isMarquee) {
context.report({
node,
message: errorMessage,
});
}
schema: [
{ type: 'object' },
],
},
});
module.exports.schema = [
{ type: 'object' },
];
create: context => ({
JSXOpeningElement: node => {
const isMarquee = elementType(node) === 'marquee';
if (isMarquee) {
context.report({
node,
message: errorMessage,
});
}
},
}),
};

@@ -21,24 +21,30 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const nodeType = elementType(node);
module.exports = {
meta: {
docs: {},
if (applicableTypes.indexOf(nodeType) === -1) {
return;
}
schema: [
{ type: 'object' },
],
},
const onChange = getProp(node.attributes, 'onChange');
const hasOnBlur = getProp(node.attributes, 'onBlur') !== undefined;
create: context => ({
JSXOpeningElement: node => {
const nodeType = elementType(node);
if (onChange && !hasOnBlur) {
context.report({
node,
message: errorMessage,
});
}
},
});
if (applicableTypes.indexOf(nodeType) === -1) {
return;
}
module.exports.schema = [
{ type: 'object' },
];
const onChange = getProp(node.attributes, 'onChange');
const hasOnBlur = getProp(node.attributes, 'onBlur') !== undefined;
if (onChange && !hasOnBlur) {
context.report({
node,
message: errorMessage,
});
}
},
}),
};

@@ -19,28 +19,34 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const { attributes } = node;
if (getProp(attributes, 'onClick') === undefined) {
return;
}
module.exports = {
meta: {
docs: {},
const type = elementType(node);
schema: [
{ type: 'object' },
],
},
if (isHiddenFromScreenReader(type, attributes)) {
return;
} else if (isInteractiveElement(type, attributes)) {
return;
} else if (getTabIndex(getProp(attributes, 'tabIndex')) !== undefined) {
return;
}
create: context => ({
JSXOpeningElement: node => {
const { attributes } = node;
if (getProp(attributes, 'onClick') === undefined) {
return;
}
context.report({
node,
message: errorMessage,
});
},
});
const type = elementType(node);
module.exports.schema = [
{ type: 'object' },
];
if (isHiddenFromScreenReader(type, attributes)) {
return;
} else if (isInteractiveElement(type, attributes)) {
return;
} else if (getTabIndex(getProp(attributes, 'tabIndex')) !== undefined) {
return;
}
context.report({
node,
message: errorMessage,
});
},
}),
};

@@ -18,29 +18,35 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
const attributes = node.attributes;
if (getProp(attributes, 'onclick') === undefined) {
return;
}
module.exports = {
meta: {
docs: {},
const type = elementType(node);
schema: [
{ type: 'object' },
],
},
if (isHiddenFromScreenReader(type, attributes)) {
return;
} else if (isInteractiveElement(type, attributes)) {
return;
} else if (getPropValue(getProp(attributes, 'role'))) {
return;
}
create: context => ({
JSXOpeningElement: node => {
const attributes = node.attributes;
if (getProp(attributes, 'onclick') === undefined) {
return;
}
// Visible, non-interactive elements require role attribute.
context.report({
node,
message: errorMessage,
});
},
});
const type = elementType(node);
module.exports.schema = [
{ type: 'object' },
];
if (isHiddenFromScreenReader(type, attributes)) {
return;
} else if (isInteractiveElement(type, attributes)) {
return;
} else if (getPropValue(getProp(attributes, 'role'))) {
return;
}
// Visible, non-interactive elements require role attribute.
context.report({
node,
message: errorMessage,
});
},
}),
};

@@ -19,44 +19,50 @@ /**

module.exports = context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
if (normalizedName !== 'ROLE') {
return;
}
schema: [
{ type: 'object' },
],
},
const value = getLiteralPropValue(attribute);
create: context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
// 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;
}
if (normalizedName !== 'ROLE') {
return;
}
const normalizedValues = String(value).toUpperCase().split(' ');
const validRoles = normalizedValues
.filter(val => Object.keys(validRoleTypes).indexOf(val) > -1);
const value = getLiteralPropValue(attribute);
validRoles.forEach(role => {
const { requiredProps } = validRoleTypes[role];
// 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;
}
if (requiredProps.length > 0) {
const hasRequiredProps = requiredProps
.every(prop => getProp(attribute.parent.attributes, prop));
const normalizedValues = String(value).toUpperCase().split(' ');
const validRoles = normalizedValues
.filter(val => Object.keys(validRoleTypes).indexOf(val) > -1);
if (hasRequiredProps === false) {
context.report({
node: attribute,
message: errorMessage(role.toLowerCase(), requiredProps),
});
validRoles.forEach(role => {
const { requiredProps } = validRoleTypes[role];
if (requiredProps.length > 0) {
const hasRequiredProps = requiredProps
.every(prop => getProp(attribute.parent.attributes, prop));
if (hasRequiredProps === false) {
context.report({
node: attribute,
message: errorMessage(role.toLowerCase(), requiredProps),
});
}
}
}
});
},
});
module.exports.schema = [
{ type: 'object' },
];
});
},
}),
};

@@ -25,42 +25,48 @@ /**

module.exports = context => ({
JSXOpeningElement: node => {
// If role is not explicitly defined, then try and get its implicit role.
const type = elementType(node);
const role = getProp(node.attributes, 'role');
const roleValue = role ? getLiteralPropValue(role) : getImplicitRole(type, node.attributes);
const isImplicit = roleValue && role === undefined;
module.exports = {
meta: {
docs: {},
// 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' || ROLES[roleValue.toUpperCase()] === undefined) {
return;
}
schema: [
{ type: 'object' },
],
},
// Make sure it has no aria-* properties defined outside of its property set.
const propertySet = ROLES[roleValue.toUpperCase()].props;
const invalidAriaPropsForRole = Object.keys(ARIA)
.filter(attribute => propertySet.indexOf(attribute) === -1);
create: context => ({
JSXOpeningElement: node => {
// If role is not explicitly defined, then try and get its implicit role.
const type = elementType(node);
const role = getProp(node.attributes, 'role');
const roleValue = role ? getLiteralPropValue(role) : getImplicitRole(type, node.attributes);
const isImplicit = roleValue && role === undefined;
node.attributes.forEach(prop => {
if (prop.type === 'JSXSpreadAttribute') {
// 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' || ROLES[roleValue.toUpperCase()] === undefined) {
return;
}
const name = propName(prop);
const normalizedName = name ? name.toUpperCase() : '';
// Make sure it has no aria-* properties defined outside of its property set.
const propertySet = ROLES[roleValue.toUpperCase()].props;
const invalidAriaPropsForRole = Object.keys(ARIA)
.filter(attribute => propertySet.indexOf(attribute) === -1);
if (invalidAriaPropsForRole.indexOf(normalizedName) > -1) {
context.report({
node,
message: errorMessage(name, roleValue, type, isImplicit),
});
}
});
},
});
node.attributes.forEach(prop => {
if (prop.type === 'JSXSpreadAttribute') {
return;
}
module.exports.schema = [
{ type: 'object' },
];
const name = propName(prop);
const normalizedName = name ? name.toUpperCase() : '';
if (invalidAriaPropsForRole.indexOf(normalizedName) > -1) {
context.report({
node,
message: errorMessage(name, roleValue, type, isImplicit),
});
}
});
},
}),
};

@@ -15,29 +15,35 @@ /**

module.exports = context => ({
JSXAttribute: node => {
const name = propName(node);
if (name && name.toUpperCase() !== 'SCOPE') {
return;
}
module.exports = {
meta: {
docs: {},
const { parent } = node;
const tagName = elementType(parent);
schema: [
{ type: 'object' },
],
},
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (Object.keys(DOMElements).indexOf(tagName) === -1) {
return;
} else if (tagName && tagName.toUpperCase() === 'TH') {
return;
}
create: context => ({
JSXAttribute: node => {
const name = propName(node);
if (name && name.toUpperCase() !== 'SCOPE') {
return;
}
context.report({
node,
message: errorMessage,
});
},
});
const { parent } = node;
const tagName = elementType(parent);
module.exports.schema = [
{ type: 'object' },
];
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (Object.keys(DOMElements).indexOf(tagName) === -1) {
return;
} else if (tagName && tagName.toUpperCase() === 'TH') {
return;
}
context.report({
node,
message: errorMessage,
});
},
}),
};

@@ -14,28 +14,34 @@ /**

module.exports = context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
module.exports = {
meta: {
docs: {},
// Check if tabIndex is the attribute
if (normalizedName !== 'TABINDEX') {
return;
}
schema: [
{ type: 'object' },
],
},
// Only check literals because we can't infer values from certain expressions.
const value = Number(getLiteralPropValue(attribute));
create: context => ({
JSXAttribute: attribute => {
const name = propName(attribute);
const normalizedName = name ? name.toUpperCase() : '';
if (isNaN(value) || value <= 0) {
return;
}
// Check if tabIndex is the attribute
if (normalizedName !== 'TABINDEX') {
return;
}
context.report({
node: attribute,
message: errorMessage,
});
},
});
// Only check literals because we can't infer values from certain expressions.
const value = Number(getLiteralPropValue(attribute));
module.exports.schema = [
{ type: 'object' },
];
if (isNaN(value) || value <= 0) {
return;
}
context.report({
node: attribute,
message: errorMessage,
});
},
}),
};

@@ -1819,3 +1819,5 @@ {

"MENUITEMCHECKBOX": {
"requiredProps": [],
"requiredProps": [
"ARIA-CHECKED"
],
"abstract": false,

@@ -1843,3 +1845,5 @@ "props": [

"MENUITEMRADIO": {
"requiredProps": [],
"requiredProps": [
"ARIA-CHECKED"
],
"abstract": false,

@@ -1990,3 +1994,5 @@ "props": [

"RADIO": {
"requiredProps": [],
"requiredProps": [
"ARIA-CHECKED"
],
"abstract": false,

@@ -1993,0 +1999,0 @@ "props": [

@@ -24,2 +24,5 @@ /**

const string = ['Word'];
const array = [['Word1', 'Word2']];
const ruleTester = new RuleTester();

@@ -29,3 +32,4 @@

message: 'Redundant alt attribute. Screen-readers already announce `img` tags as an image. ' +
'You don\'t need to use the words `image`, `photo,` or `picture` in the alt prop.',
'You don\'t need to use the words `image`, `photo,` or `picture` ' +
'(or any specified custom words) in the alt prop.',
type: 'JSXOpeningElement',

@@ -118,3 +122,10 @@ };

},
// TESTS FOR STRING OPTION
{ code: '<img alt="Word" />;', options: string, errors: [expectedError], parserOptions },
// TESTS FOR ARRAY OPTION TESTS
{ code: '<img alt="Word1" />;', options: array, errors: [expectedError], parserOptions },
{ code: '<img alt="Word2" />;', options: array, errors: [expectedError], parserOptions },
],
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc