Skip to content

Commit 5988218

Browse files
author
Ethan Cohen
committed
Add redundant-alt rule.
1 parent 65bbc36 commit 5988218

File tree

7 files changed

+133
-2
lines changed

7 files changed

+133
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Then configure the rules you want to use under the rules section.
6767
- [use-onblur-not-onchange](docs/rules/use-onblur-not-onchange.md): Enforce that onBlur is used instead of onChange.
6868
- [no-access-key](docs/rules/no-access-key.md): Enforce that the accessKey prop is not used on any element to avoid complications with keyboard commands used by a screenreader.
6969
- [use-label-for](docs/rules/use-label-for.md): Enforce that label elements have the htmlFor attribute
70+
- [redundant-alt](docs/rules/redundant-alt.md): Enforce img alt attribute does not contain the word image, picture, or photo.
7071

7172
## License
7273

docs/rules/img-uses-alt.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This rule takes no arguments. However, note that passing props as spread attribu
88

99
### Succeed
1010
```jsx
11-
<img src="foo" alt="An image of foo!" />
11+
<img src="foo" alt="Foo eating a sandwich." />
1212
```
1313

1414
### Fail

docs/rules/redundant-alt.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# redundant-alt
2+
3+
Enforce img alt attribute does not contain the word image, picture, or photo. Screenreaders already announce `img` elements as an image. There is no need to use words such as *image*, *photo*, and/or *picture*.
4+
5+
## Rule details
6+
7+
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.
8+
9+
### Succeed
10+
```jsx
11+
<img src="foo" alt="Foo eating a sandwich." />
12+
<img src="bar" aria-hidden alt="Picture of me taking a photo of an image" /> // Will pass because it is hidden.
13+
```
14+
15+
### Fail
16+
```jsx
17+
<img src="foo" alt="Photo of foo being weird." />
18+
<img src="bar" alt="Image of me at a bar!" />
19+
<img src="baz" alt="Picture of baz fixing a bug." />
20+
```

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.1.2",
3+
"version": "0.2.0",
44
"description": "A static analysis linter of jsx and their accessibility with screen readers.",
55
"keywords": [
66
"eslint",

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module.exports = {
44
rules: {
55
'img-uses-alt': require('./rules/img-uses-alt'),
6+
'redundant-alt': require('./rules/redundant-alt'),
67
'onclick-uses-role': require('./rules/onclick-uses-role'),
78
'mouse-events-map-to-key-events': require('./rules/mouse-events-map-to-key-events'),
89
'use-onblur-not-onchange': require('./rules/use-onblur-not-onchange'),

src/rules/redundant-alt.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @fileoverview Enforce img alt attribute does not have the word image, picture, or photo.
3+
* @author Ethan Cohen
4+
*/
5+
'use strict';
6+
7+
// ----------------------------------------------------------------------------
8+
// Rule Definition
9+
// ----------------------------------------------------------------------------
10+
11+
import hasAttribute from '../util/hasAttribute';
12+
import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader';
13+
14+
const REDUNDANT_WORDS = [
15+
'image',
16+
'photo',
17+
'picture'
18+
];
19+
20+
const errorMessage = 'Redundant alt attribute. Screen-readers already announce `img` tags as an image. ' +
21+
'You don\'t need to use the words `image`, `photo,` or `picture` in the alt prop.';
22+
23+
module.exports = context => ({
24+
JSXOpeningElement: node => {
25+
const type = node.name.name;
26+
if (type.toUpperCase() !== 'IMG') {
27+
return;
28+
}
29+
30+
const hasAltProp = hasAttribute(node.attributes, 'alt');
31+
const isVisible = isHiddenFromScreenReader(node.attributes) === false;
32+
33+
if (Boolean(hasAltProp) && isVisible) {
34+
const hasRedundancy = REDUNDANT_WORDS.some(word => Boolean(hasAltProp.value.value.match(new RegExp(word, 'gi'))));
35+
36+
if (hasRedundancy === true) {
37+
context.report({
38+
node,
39+
message: errorMessage
40+
});
41+
}
42+
43+
return;
44+
}
45+
}
46+
});
47+
48+
module.exports.schema = [
49+
{ type: 'object' }
50+
];

tests/src/rules/redundant-alt.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @fileoverview Enforce img alt attribute does not have the word image, picture, or photo.
3+
* @author Ethan Cohen
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
import rule from '../../../src/rules/redundant-alt';
13+
import { RuleTester } from 'eslint';
14+
15+
const parserOptions = {
16+
ecmaVersion: 6,
17+
ecmaFeatures: {
18+
jsx: true
19+
}
20+
};
21+
22+
// -----------------------------------------------------------------------------
23+
// Tests
24+
// -----------------------------------------------------------------------------
25+
26+
const ruleTester = new RuleTester();
27+
28+
const expectedError = {
29+
message: 'Redundant alt attribute. Screen-readers already announce `img` tags as an image. ' +
30+
'You don\'t need to use the words `image`, `photo,` or `picture` in the alt prop.',
31+
type: 'JSXOpeningElement'
32+
};
33+
34+
ruleTester.run('img-uses-alt', rule, {
35+
valid: [
36+
{ code: '<img alt="foo" />;', parserOptions },
37+
{ code: '<img alt="picture of me taking a photo of an image" aria-hidden />', parserOptions },
38+
{ code: '<img aria-hidden alt="photo of image" />', parserOptions },
39+
{ code: '<img ALt="foo" />;', parserOptions },
40+
{ code: '<img {...this.props} alt="foo" />', parserOptions },
41+
{ code: '<a />', parserOptions },
42+
{ code: '<img />', parserOptions },
43+
{ code: '<img aria-hidden={false} alt="Doing cool things." />', parserOptions }
44+
],
45+
invalid: [
46+
{ code: '<img alt="Photo of friend." />;', errors: [ expectedError ], parserOptions },
47+
{ code: '<img alt="Picture of friend." />;', errors: [ expectedError ], parserOptions },
48+
{ code: '<img alt="Image of friend." />;', errors: [ expectedError ], parserOptions },
49+
{ code: '<img alt="PhOtO of friend." />;', errors: [ expectedError ], parserOptions },
50+
{ code: '<img alt="piCTUre of friend." />;', errors: [ expectedError ], parserOptions },
51+
{ code: '<img alt="imAGE of friend." />;', errors: [ expectedError ], parserOptions },
52+
{ code: '<img alt="photo of cool person" aria-hidden={false} />', errors: [ expectedError ], parserOptions },
53+
{ code: '<img alt="picture of cool person" aria-hidden={false} />', errors: [ expectedError ], parserOptions },
54+
{ code: '<img alt="image of cool person" aria-hidden={false} />', errors: [ expectedError ], parserOptions },
55+
{ code: '<img alt="photo" {...this.props} />', errors: [ expectedError ], parserOptions },
56+
{ code: '<img alt="image" {...this.props} />', errors: [ expectedError ], parserOptions },
57+
{ code: '<img alt="picture" {...this.props} />', errors: [ expectedError ], parserOptions }
58+
]
59+
});

0 commit comments

Comments
 (0)