Skip to content

Commit 81598c9

Browse files
author
Ethan Cohen
committed
2 parents d43fe11 + 4fabf91 commit 81598c9

File tree

7 files changed

+117
-65
lines changed

7 files changed

+117
-65
lines changed

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
0.6.2 / 2016-04-08
2+
==================
3+
- [fix] Fix rule details for img-uses-alt: allow alt="" or role="presentation".
4+
5+
6+
0.6.1 / 2016-04-07
7+
==================
8+
- [fix] Do not infer interactivity of components that are not low-level DOM elements.
9+
10+
11+
0.6.0 / 2016-04-06
12+
==================
13+
- [breaking] Allow alt="" when role="presentation" on img-uses-alt rule.
14+
- [new] More descriptive error messaging for img-uses-alt rule.
15+
16+
17+
0.5.2 / 2016-04-05
18+
==================
19+
- [fix] Handle token lists for valid-aria-role.
20+
21+
22+
0.5.1 / 2016-04-05
23+
==================
24+
- [fix] Handle null valued props for valid-aria-role.
25+
26+
27+
0.5.0 / 2016-04-02
28+
==================
29+
- [new] Implement valid-aria-role rule. Based on [AX_ARIA_01](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_aria_01)
30+
31+
32+
0.4.3 / 2016-03-29
33+
==================
34+
- [fix] Handle LogicalExpression attribute types when extracting values. LogicalExpressions are of form `<Component prop={foo || "foobar"} />`
35+
36+
37+
0.4.2 / 2016-03-24
38+
==================
39+
- [fix] Allow component names of form `Object.Property` i.e. `UX.Layout`
40+
41+
42+
0.3.0 / 2016-03-02
43+
==================
44+
- [new] Implement [no-hash-href](docs/rules/no-hash-href.md) rule.
45+
- [fix] Fixed TemplateLiteral AST value building to get more exact values from template strings.
46+
47+
48+
0.2.0 / 2016-03-01
49+
==================
50+
- [new] Implement [redunant-alt](docs/rules/redundant-alt.md) rule.
51+
52+
53+
0.1.2 / 2016-03-01
54+
==================
55+
- Initial pre-release.

docs/rules/img-uses-alt.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ To tell this plugin to also check your `Image` element, specify this in your `.e
4141
}
4242
```
4343

44-
Note that passing props as spread attribute without `alt` explicitly defined will cause this rule to fail. Explicitly pass down `alt` prop for rule to pass. The prop must have an actual value to pass. Use `Image` component above as a reference for destructuring and applying the prop. **It is a good thing to explicitly pass props that you expect to be passed for self-documentation.**
44+
Note that passing props as spread attribute without `alt` explicitly defined will cause this rule to fail. Explicitly pass down `alt` prop or use `role="presentation"` for rule to pass. Use `Image` component above as a reference for destructuring and applying the prop. **It is a good thing to explicitly pass props that you expect to be passed for self-documentation.**
4545

4646
### Succeed
4747
```jsx
4848
<img src="foo" alt="Foo eating a sandwich." />
4949
<img src="foo" alt={"Foo eating a sandwich."} />
5050
<img src="foo" alt={altText} />
5151
<img src="foo" alt={`${person} smiling`} />
52-
<img src="foo" alt="" role="presentation" /> <!-- Alt text can be an empty string if `role="presentation"` -->
52+
<img src="foo" alt="" />
53+
<img src="foo" role="presentation" />
5354
```
5455

5556
### Fail

docs/rules/valid-aria-role.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Elements with ARIA roles must use a valid, non-abstract ARIA role. A reference to all role defintions can be found at [WAI-ARIA](https://www.w3.org/TR/wai-aria/roles#role_definitions) site.
44

5+
[AX_ARIA_01](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_aria_01)
6+
57
## Rule details
68

79
This rule takes no arguments.

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.6.1",
3+
"version": "0.6.2",
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: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,39 +22,38 @@ module.exports = context => ({
2222
return;
2323
}
2424

25+
const hasRoleProp = hasAttribute(node.attributes, 'role');
26+
const roleValue = getAttributeValue(hasRoleProp);
27+
const isPresentation = hasRoleProp && roleValue.toLowerCase() === 'presentation';
28+
29+
if (isPresentation) {
30+
return;
31+
}
32+
2533
const hasAltProp = hasAttribute(node.attributes, 'alt');
2634

2735
// Missing alt prop error.
2836
if (!hasAltProp) {
2937
context.report({
3038
node,
31-
message: `${nodeType} elements must have an alt prop.`
39+
message: `${nodeType} elements must have an alt prop or use role="presentation".`
3240
});
3341
return;
3442
}
3543

3644
// Check if alt prop is undefined.
37-
const altProp = getAttributeValue(hasAltProp);
38-
39-
// Check if alt prop is ""
40-
const emptyAlt = hasAltProp && hasAltProp.value
41-
&& hasAltProp.value.type === 'Literal'
42-
&& hasAltProp.value.value === "";
43-
44-
const hasRoleProp = hasAttribute(node.attributes, 'role');
45-
const roleProp = getAttributeValue(hasRoleProp);
45+
const altValue = getAttributeValue(hasAltProp);
4646

47-
// Allow altProp to be "" if `role="presentation"` is present.
48-
const isValid = altProp || (emptyAlt && hasRoleProp && roleProp.toUpperCase() === 'PRESENTATION');
49-
50-
// Undefined alt prop error.
51-
if (!isValid) {
52-
context.report({
53-
node,
54-
message: `${nodeType} alt prop must have a value. You can set alt="" if role="presentation" is applied.`
55-
});
47+
if (altValue || altValue === '') {
5648
return;
5749
}
50+
51+
// Undefined alt prop error.
52+
context.report({
53+
node,
54+
message:
55+
`Invalid alt value for ${nodeType}. Use alt="" or role="presentation" for presentational images.`
56+
});
5857
}
5958
});
6059

src/util/getAttributeValue.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import buildTemplateLiteral from './buildTemplateLiteral';
44

55
const getValue = value => {
66
if (value.type === 'Literal') {
7-
return value.value === "" ? undefined : value.value;
7+
return value.value;
88
} else if (value.type === 'Identifier') {
99
return value.name === "" ? undefined : value.name;
1010
} else if (value.type === 'JSXElement') {
@@ -17,7 +17,7 @@ const getValue = value => {
1717

1818
switch (type) {
1919
case 'Literal':
20-
return obj.value === "" ? undefined : obj.value;
20+
return obj.value;
2121
case 'TemplateLiteral':
2222
return buildTemplateLiteral(obj);
2323
case 'Identifier':

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

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,16 @@ const parserOptions = {
2525

2626
const ruleTester = new RuleTester();
2727

28-
const customMissingPropError = type => ({
29-
message: `${type} elements must have an alt prop.`,
28+
const missingPropError = type => ({
29+
message: `${type} elements must have an alt prop or use role="presentation".`,
3030
type: 'JSXOpeningElement'
3131
});
3232

33-
const customAltValueError = type => ({
34-
message: `${type} alt prop must have a value. You can set alt="" if role="presentation" is applied.`,
33+
const altValueError = type => ({
34+
message: `Invalid alt value for ${type}. Use alt="" or role="presentation" for presentational images.`,
3535
type: 'JSXOpeningElement'
3636
});
3737

38-
const expectedMissingPropError = customMissingPropError('img');
39-
const expectedAltValueError = customAltValueError('img');
40-
4138
const string = [ 'Avatar' ];
4239
const array = [ [ 'Thumbnail', 'Image' ] ];
4340

@@ -65,11 +62,15 @@ ruleTester.run('img-uses-alt', rule, {
6562
{ code: '<img alt={foo.bar || ""} />', parserOptions },
6663
{ code: '<img alt={bar() || ""} />', parserOptions },
6764
{ code: '<img alt={foo.bar() || ""} />', parserOptions },
68-
{ code: '<img alt="" role="presentation" />', parserOptions }, // Allow alt to be undefined if role="presentation"
65+
{ code: '<img alt="" />', parserOptions },
66+
{ code: '<img alt=" " />', parserOptions },
67+
{ code: '<img alt="" role="presentation" />', parserOptions },
6968
{ code: '<img alt="" role={`presentation`} />', parserOptions },
7069
{ code: '<img alt="" role={"presentation"} />', parserOptions },
70+
{ code: '<img role="presentation" />;', parserOptions },
71+
{ code: '<img alt={undefined} role="presentation" />;', parserOptions },
72+
{ code: '<img alt role="presentation" />;', parserOptions },
7173
{ code: '<img alt="this is lit..." role="presentation" />', parserOptions },
72-
{ code: '<img alt=" " />', parserOptions }, // For decorative images.
7374

7475
// CUSTOM ELEMENT TESTS FOR STRING OPTION
7576
{ code: '<Avatar alt="foo" />;', options: string, parserOptions },
@@ -86,6 +87,7 @@ ruleTester.run('img-uses-alt', rule, {
8687
{ code: '<Avatar alt={() => void 0} />', options: string, parserOptions },
8788
{ code: '<AVATAR />', options: string, parserOptions },
8889
{ code: '<Avatar alt={alt || "foo" } />', options: string, parserOptions },
90+
{ code: '<Avatar alt="" />', options: string, parserOptions },
8991

9092
// CUSTOM ELEMENT TESTS FOR ARRAY OPTION TESTS
9193
{ code: '<Thumbnail alt="foo" />;', options: array, parserOptions },
@@ -119,66 +121,59 @@ ruleTester.run('img-uses-alt', rule, {
119121
],
120122
invalid: [
121123
// DEFAULT ELEMENT 'img' TESTS
122-
{ code: '<img />;', errors: [ expectedMissingPropError ], parserOptions },
123-
{ code: '<img alt />;', errors: [ expectedAltValueError ], parserOptions },
124-
{ code: '<img alt={undefined} />;', errors: [ expectedAltValueError ], parserOptions },
125-
{ code: '<img alt={`${undefined}`} />;', errors: [ expectedAltValueError ], parserOptions },
126-
{ code: '<img alt="" />;', errors: [ expectedAltValueError ], parserOptions },
127-
{ code: '<img role="presentation" />;', errors: [ expectedMissingPropError ], parserOptions },
128-
{ code: '<img alt={undefined} role="presentation" />;', errors: [ expectedAltValueError ], parserOptions },
129-
{ code: '<img alt role="presentation" />;', errors: [ expectedAltValueError ], parserOptions },
130-
{ code: '<img src="xyz" />', errors: [ expectedMissingPropError ], parserOptions },
131-
{ code: '<img {...this.props} />', errors: [ expectedMissingPropError ], parserOptions },
132-
{ code: '<img alt={false || false} />', errors: [ expectedAltValueError ], parserOptions },
124+
{ code: '<img />;', errors: [ missingPropError('img') ], parserOptions },
125+
{ code: '<img alt />;', errors: [ altValueError('img') ], parserOptions },
126+
{ code: '<img alt={undefined} />;', errors: [ altValueError('img') ], parserOptions },
127+
{ code: '<img alt={`${undefined}`} />;', errors: [ altValueError('img') ], parserOptions },
128+
{ code: '<img src="xyz" />', errors: [ missingPropError('img') ], parserOptions },
129+
{ code: '<img {...this.props} />', errors: [ missingPropError('img') ], parserOptions },
130+
{ code: '<img alt={false || false} />', errors: [ altValueError('img') ], parserOptions },
133131

134132
// CUSTOM ELEMENT TESTS FOR STRING OPTION
135133
{
136134
code: '<Avatar />;',
137-
errors: [ customMissingPropError('Avatar') ],
135+
errors: [ missingPropError('Avatar') ],
138136
options: string,
139137
parserOptions
140138
},
141-
{ code: '<Avatar alt />;', errors: [ customAltValueError('Avatar') ], options: string, parserOptions },
142-
{ code: '<Avatar alt={undefined} />;', errors: [ customAltValueError('Avatar') ], options: string, parserOptions },
139+
{ code: '<Avatar alt />;', errors: [ altValueError('Avatar') ], options: string, parserOptions },
140+
{ code: '<Avatar alt={undefined} />;', errors: [ altValueError('Avatar') ], options: string, parserOptions },
143141
{
144142
code: '<Avatar alt={`${undefined}`} />;',
145-
errors: [ customAltValueError('Avatar') ],
143+
errors: [ altValueError('Avatar') ],
146144
options: string,
147145
parserOptions
148146
},
149-
{ code: '<Avatar alt="" />;', errors: [ customAltValueError('Avatar') ], options: string, parserOptions },
150-
{ code: '<Avatar src="xyz" />', errors: [ customMissingPropError('Avatar') ], options: string, parserOptions },
151-
{ code: '<Avatar {...this.props} />', errors: [ customMissingPropError('Avatar') ], options: string, parserOptions },
147+
{ code: '<Avatar src="xyz" />', errors: [ missingPropError('Avatar') ], options: string, parserOptions },
148+
{ code: '<Avatar {...this.props} />', errors: [ missingPropError('Avatar') ], options: string, parserOptions },
152149

153150
// CUSTOM ELEMENT TESTS FOR ARRAY OPTION TESTS
154-
{ code: '<Thumbnail />;', errors: [ customMissingPropError('Thumbnail') ], options: array, parserOptions },
155-
{ code: '<Thumbnail alt />;', errors: [ customAltValueError('Thumbnail') ], options: array, parserOptions },
151+
{ code: '<Thumbnail />;', errors: [ missingPropError('Thumbnail') ], options: array, parserOptions },
152+
{ code: '<Thumbnail alt />;', errors: [ altValueError('Thumbnail') ], options: array, parserOptions },
156153
{
157154
code: '<Thumbnail alt={undefined} />;',
158-
errors: [ customAltValueError('Thumbnail') ],
155+
errors: [ altValueError('Thumbnail') ],
159156
options: array,
160157
parserOptions
161158
},
162159
{
163160
code: '<Thumbnail alt={`${undefined}`} />;',
164-
errors: [ customAltValueError('Thumbnail') ],
161+
errors: [ altValueError('Thumbnail') ],
165162
options: array,
166163
parserOptions
167164
},
168-
{ code: '<Thumbnail alt="" />;', errors: [ customAltValueError('Thumbnail') ], options: array, parserOptions },
169-
{ code: '<Thumbnail src="xyz" />', errors: [ customMissingPropError('Thumbnail') ], options: array, parserOptions },
165+
{ code: '<Thumbnail src="xyz" />', errors: [ missingPropError('Thumbnail') ], options: array, parserOptions },
170166
{
171167
code: '<Thumbnail {...this.props} />',
172-
errors: [ customMissingPropError('Thumbnail') ],
168+
errors: [ missingPropError('Thumbnail') ],
173169
options: array,
174170
parserOptions
175171
},
176-
{ code: '<Image />;', errors: [ customMissingPropError('Image') ], options: array, parserOptions },
177-
{ code: '<Image alt />;', errors: [ customAltValueError('Image') ], options: array, parserOptions },
178-
{ code: '<Image alt={undefined} />;', errors: [ customAltValueError('Image') ], options: array, parserOptions },
179-
{ code: '<Image alt={`${undefined}`} />;', errors: [ customAltValueError('Image') ], options: array, parserOptions },
180-
{ code: '<Image alt="" />;', errors: [ customAltValueError('Image') ], options: array, parserOptions },
181-
{ code: '<Image src="xyz" />', errors: [ customMissingPropError('Image') ], options: array, parserOptions },
182-
{ code: '<Image {...this.props} />', errors: [ customMissingPropError('Image') ], options: array, parserOptions }
172+
{ code: '<Image />;', errors: [ missingPropError('Image') ], options: array, parserOptions },
173+
{ code: '<Image alt />;', errors: [ altValueError('Image') ], options: array, parserOptions },
174+
{ code: '<Image alt={undefined} />;', errors: [ altValueError('Image') ], options: array, parserOptions },
175+
{ code: '<Image alt={`${undefined}`} />;', errors: [ altValueError('Image') ], options: array, parserOptions },
176+
{ code: '<Image src="xyz" />', errors: [ missingPropError('Image') ], options: array, parserOptions },
177+
{ code: '<Image {...this.props} />', errors: [ missingPropError('Image') ], options: array, parserOptions }
183178
]
184179
});

0 commit comments

Comments
 (0)