Skip to content

Commit 8086b92

Browse files
author
Ethan Cohen
committed
Fix - check value of props to determine their existence because undefined is not really there.
Remove case normalizing as well for html elements. lower case convention applies. If user has custom Label component, it should not look for htmlFor, for example.
1 parent 5988218 commit 8086b92

15 files changed

+96
-32
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-jsx-a11y",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "A static analysis linter of jsx and their accessibility with screen readers.",
55
"keywords": [
66
"eslint",

src/rules/img-uses-alt.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const errorMessage = 'img elements must have an alt tag.';
1515
module.exports = context => ({
1616
JSXOpeningElement: node => {
1717
const type = node.name.name;
18-
if (type.toUpperCase() !== 'IMG') {
18+
if (type !== 'img') {
1919
return;
2020
}
2121

src/rules/redundant-alt.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ const errorMessage = 'Redundant alt attribute. Screen-readers already announce `
2323
module.exports = context => ({
2424
JSXOpeningElement: node => {
2525
const type = node.name.name;
26-
if (type.toUpperCase() !== 'IMG') {
26+
if (type !== 'img') {
2727
return;
2828
}
2929

30-
const hasAltProp = hasAttribute(node.attributes, 'alt');
30+
const altProp = hasAttribute(node.attributes, 'alt');
3131
const isVisible = isHiddenFromScreenReader(node.attributes) === false;
3232

33-
if (Boolean(hasAltProp) && isVisible) {
34-
const hasRedundancy = REDUNDANT_WORDS.some(word => Boolean(hasAltProp.value.value.match(new RegExp(word, 'gi'))));
33+
if (Boolean(altProp) && typeof altProp === 'string' && isVisible) {
34+
const hasRedundancy = REDUNDANT_WORDS.some(word => Boolean(altProp.match(new RegExp(word, 'gi'))));
3535

3636
if (hasRedundancy === true) {
3737
context.report({

src/rules/use-label-for.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const errorMessage = 'Form controls using a label to identify them must be ' +
1616
module.exports = context => ({
1717
JSXOpeningElement: node => {
1818
const type = node.name.name;
19-
if (type.toUpperCase() !== 'LABEL') {
19+
if (type !== 'label') {
2020
return;
2121
}
2222

src/util/getAttributeValue.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
const getAttributeValue = attribute => {
4+
if (attribute.value === null) {
5+
return null;
6+
} else if (attribute.type === 'JSXAttribute') {
7+
8+
if (attribute.value.type === 'Literal') {
9+
return attribute.value.value;
10+
} else if (attribute.value.type === 'JSXExpressionContainer') {
11+
const expression = attribute.value.expression;
12+
13+
switch (expression.type) {
14+
case 'Literal':
15+
return expression.value;
16+
case 'Identifier':
17+
return expression.name == 'undefined' ? undefined : expression.name;
18+
case 'ArrowFunctionExpression':
19+
case 'FunctionExpression':
20+
return () => void 0;
21+
default:
22+
return undefined;
23+
}
24+
}
25+
}
26+
27+
return undefined;
28+
};
29+
30+
export default getAttributeValue;

src/util/hasAttribute.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
'use strict';
22

3+
import getAttributeValue from './getAttributeValue';
4+
35
const hasAttribute = (attributes, attribute) => {
4-
let idx = 0;
6+
let value = false;
57

6-
const hasAttr = attributes.some((attr, index) => {
8+
const hasAttr = attributes.some(attr => {
79
// If the attributes contain a spread attribute, then skip.
810
if (attr.type === 'JSXSpreadAttribute') {
911
return false;
1012
}
1113

1214
// Normalize.
1315
if (attr.name.name.toUpperCase() === attribute.toUpperCase()) {
14-
idx = index; // Keep track of the index.
15-
return true;
16+
value = getAttributeValue(attr);
17+
18+
// If the value is undefined, it doesn't really have the attribute.
19+
return value !== undefined;
1620
}
1721

1822
return false;
1923
});
2024

21-
return hasAttr ? attributes[idx] : false;
25+
return hasAttr ? value : false;
2226
};
2327

2428
export default hasAttribute;

src/util/isHiddenFromScreenReader.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
'use strict';
22

3-
const isHiddenFromScreenReader = attributes => (
4-
attributes.some(attribute => {
5-
if (attribute.type === 'JSXSpreadAttribute') {
6-
return false;
7-
}
3+
import hasAttribute from './hasAttribute';
84

9-
const name = attribute.name.name.toUpperCase();
10-
const value = attribute.value && attribute.value.value;
11-
12-
return name === 'ARIA-HIDDEN' && (value === true || value === null);
13-
})
14-
);
5+
const isHiddenFromScreenReader = attributes => {
6+
const hasAriaHidden = hasAttribute(attributes, 'aria-hidden');
7+
return hasAriaHidden && (hasAriaHidden === true || hasAriaHidden === null);
8+
};
159

1610
export default isHiddenFromScreenReader;

src/util/isInteractiveElement.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const interactiveMap = {
1111
button: () => true,
1212
input: attributes => {
1313
const hasTypeAttr = hasAttribute(attributes, 'type');
14-
return hasTypeAttr ? hasTypeAttr.value.value.toUpperCase() !== 'HIDDEN' : true;
14+
return hasTypeAttr ? hasTypeAttr.toUpperCase() !== 'HIDDEN' : true;
1515
},
1616
option: () => true,
1717
select: () => true,

tests/src/rules/img-uses-alt.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,21 @@ const expectedError = {
3333
ruleTester.run('img-uses-alt', rule, {
3434
valid: [
3535
{ code: '<img alt="foo" />;', parserOptions },
36+
{ code: '<img alt={"foo"} />;', parserOptions },
37+
{ code: '<img alt={alt} />;', parserOptions },
3638
{ code: '<img ALT="foo" />;', parserOptions },
3739
{ code: '<img ALt="foo" />;', parserOptions },
40+
{ code: '<img alt="foo" salt={undefined} />;', parserOptions },
3841
{ code: '<img {...this.props} alt="foo" />', parserOptions },
39-
{ code: '<a />', parserOptions }
42+
{ code: '<a />', parserOptions },
43+
{ code: '<img alt={function(e) {} } />', parserOptions },
44+
{ code: '<div alt={function(e) {} } />', parserOptions },
45+
{ code: '<img alt={() => void 0} />', parserOptions },
46+
{ code: '<IMG />', parserOptions }
4047
],
4148
invalid: [
4249
{ code: '<img />;', errors: [ expectedError ], parserOptions },
50+
{ code: '<img alt={undefined} />;', errors: [ expectedError ], parserOptions },
4351
{ code: '<img src="xyz" />', errors: [ expectedError ], parserOptions },
4452
{ code: '<img {...this.props} />', errors: [ expectedError ], parserOptions }
4553
]

tests/src/rules/mouse-events-map-to-key-events.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,23 @@ const mouseOutError = {
3535
type: 'JSXOpeningElement'
3636
};
3737

38-
ruleTester.run('mouseEvents-require-keyEvents', rule, {
38+
ruleTester.run('mouse-events-map-to-key-events', rule, {
3939
valid: [
4040
{ code: '<div onMouseOver={() => void 0} onFocus={() => void 0} />;', parserOptions },
4141
{ code: '<div onMouseOver={() => void 0} onFocus={() => void 0} {...props} />;', parserOptions },
42+
{ code: '<div onMouseOver={handleMouseOver} onFocus={handleFocus} />;', parserOptions },
43+
{ code: '<div onMouseOver={handleMouseOver} onFocus={handleFocus} {...props} />;', parserOptions },
4244
{ code: '<div />;', parserOptions },
4345
{ code: '<div onMouseOut={() => void 0} onBlur={() => void 0} />', parserOptions },
44-
{ code: '<div onMouseOut={() => void 0} onBlur={() => void 0} {...props} />', parserOptions }
46+
{ code: '<div onMouseOut={() => void 0} onBlur={() => void 0} {...props} />', parserOptions },
47+
{ code: '<div onMouseOut={handleMouseOut} onBlur={handleOnBlur} />', parserOptions },
48+
{ code: '<div onMouseOut={handleMouseOut} onBlur={handleOnBlur} {...props} />', parserOptions }
4549
],
4650
invalid: [
4751
{ code: '<div onMouseOver={() => void 0} />;', errors: [ mouseOverError ], parserOptions },
4852
{ code: '<div onMouseOut={() => void 0} />', errors: [ mouseOutError ], parserOptions },
53+
{ code: '<div onMouseOver={() => void 0} onFocus={undefined} />;', errors: [ mouseOverError ], parserOptions },
54+
{ code: '<div onMouseOut={() => void 0} onBlur={undefined} />', errors: [ mouseOutError ], parserOptions },
4955
{ code: '<div onMouseOver={() => void 0} {...props} />', errors: [ mouseOverError ], parserOptions },
5056
{ code: '<div onMouseOut={() => void 0} {...props} />', errors: [ mouseOutError ], parserOptions }
5157
]

0 commit comments

Comments
 (0)