Skip to content

Commit e45c6b0

Browse files
committed
Implement custom type options for certain rules.
Also, fix variable checking for template literals so you can use variable names that should not be found as strings in redundant-alt
1 parent 6cdc8eb commit e45c6b0

15 files changed

+369
-31
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ module.exports = {
6060
"SwitchCase": 1
6161
} ],
6262
"jsx-quotes": [ 2, "prefer-double" ],
63-
"max-len": [ 2, 120, 2, {
63+
"max-len": [ 2, 125, 2, {
6464
"ignorePattern": "((^import[^;]+;$)|(^\\s*it\\())",
6565
"ignoreUrls": true
6666
} ],

docs/rules/img-uses-alt.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,47 @@
11
# img-uses-alt
22

3-
Enforce that an `img` element contains the `alt` prop. The alt attribute specifies an alternate text for an image, if the image cannot be displayed.
3+
Enforce that an `img` element contains the `alt` prop. The `alt` attribute specifies an alternate text for an image, if the image cannot be displayed.
44

55
## Rule details
66

7-
This rule takes no arguments. However, 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. Alt must have an actual value to pass.
7+
This rule takes one optional argument of type string or an array of strings. These strings determine which JSX elements should be checked for the `alt` prop **including** `img` by default. This is a good use case when you have a wrapper component that simply renders an `img` element (like in React):
8+
9+
```js
10+
// Image.js
11+
const Image = props => {
12+
const {
13+
alt,
14+
...otherProps
15+
} = props;
16+
17+
return (
18+
<img alt={alt} {...otherProps} />
19+
);
20+
}
21+
22+
...
23+
24+
// Header.js (for example)
25+
...
26+
return (
27+
<header>
28+
<Image alt="Logo" src="logo.jpg" />
29+
</header>
30+
);
31+
```
32+
33+
To tell this plugin to also check your `Image` element, specify this in your `.eslintrc` file:
34+
35+
```json
36+
{
37+
"rules": {
38+
"jsx-a11y/img-uses-alt": [ 2, "Image" ], // OR
39+
"jsx-a11y/img-uses-alt": [ 2, [ "Image", "Avatar" ] ]
40+
}
41+
}
42+
```
43+
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.**
845

946
### Succeed
1047
```jsx

docs/rules/no-hash-href.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,45 @@ Enforce an anchor element's href prop value is not just #. You should use someth
44

55
## Rule details
66

7-
This rule takes no arguments.
7+
This rule takes one optional argument of type string or an array of strings. These strings determine which JSX elements should be checked for the `href` prop **including** `a` by default. This is a good use case when you have a wrapper component that simply renders an `a` element (like in React):
8+
9+
```js
10+
// Link.js
11+
const Link = props => <a {...props}>A link</a>;
12+
13+
...
14+
15+
// NavBar.js (for example)
16+
...
17+
return (
18+
<nav>
19+
<Link href="/home" />
20+
</nav>
21+
);
22+
```
23+
24+
To tell this plugin to also check your `Link` element, specify this in your `.eslintrc` file:
25+
26+
```json
27+
{
28+
"rules": {
29+
"jsx-a11y/no-hash-href": [ 2, "Link" ], // OR
30+
"jsx-a11y/no-hash-href": [ 2, [ "Link", "Anchor" ] ]
31+
}
32+
}
33+
```
834

935
### Succeed
1036
```jsx
1137
<a href="https://github.com" />
1238
<a href="#section" />
1339
<a href="foo" />
40+
<a href={undefined} /> // This check will pass, but WTF?
1441
```
1542

1643
### Fail
1744
```jsx
1845
<a href="#" />
1946
<a href={"#"} />
2047
<a href={`#`} />
21-
```
48+
```

docs/rules/redundant-alt.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ This rule takes no arguments. This rule will first check if `aria-hidden` is tru
1010
```jsx
1111
<img src="foo" alt="Foo eating a sandwich." />
1212
<img src="bar" aria-hidden alt="Picture of me taking a photo of an image" /> // Will pass because it is hidden.
13+
<img src="baz" alt={`Baz taking a ${photo}`} /> // This is valid since photo is a variable name.
1314
```
1415

1516
### Fail
1617
```jsx
1718
<img src="foo" alt="Photo of foo being weird." />
1819
<img src="bar" alt="Image of me at a bar!" />
1920
<img src="baz" alt="Picture of baz fixing a bug." />
20-
```
21+
```

docs/rules/use-label-for.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,45 @@ Enforce label tags have htmlFor attribute. Form controls using a label to identi
44

55
## Rule details
66

7-
This rule takes no arguments.
7+
This rule takes one optional argument of type string or array of strings. These strings determine which JSX elements should be checked for the `htmlFor` prop including `label` by default. This is a good use case when you have a wrapper component that simply renders an `label` element (like in React):
8+
9+
```js
10+
// Label.js
11+
const Label = props => {
12+
const {
13+
htmlFor,
14+
...otherProps
15+
} = props;
16+
17+
return (
18+
<label htmlFor={htmlFor} {...otherProps} />
19+
);
20+
}
21+
22+
...
23+
24+
// CreateAccount.js (for example)
25+
...
26+
return (
27+
<form>
28+
<input id="firstName" type="text" />
29+
<Label htmlFor="firstName">First Name</Label>
30+
</form>
31+
);
32+
```
33+
34+
To tell this plugin to also check your `Label` element, specify this in your `.eslintrc` file:
35+
36+
```json
37+
{
38+
"rules": {
39+
"jsx-a11y/use-label-for": [ 2, "Label" ], // OR
40+
"jsx-a11y/use-label-for": [ 2, [ "Label", "InputDescriptor" ] ]
41+
}
42+
}
43+
```
44+
45+
Note that passing props as spread attribute without `htmlFor` explicitly defined will cause this rule to fail. Explicitly pass down `htmlFor` prop for rule to pass. The prop must have an actual value to pass. Use `Label` component above as a reference. **It is a good thing to explicitly pass props that you expect to be passed for self-documentation.**
846

947
### Succeed
1048
```jsx
@@ -16,4 +54,4 @@ This rule takes no arguments.
1654
```jsx
1755
<input type="text" id="firstName" />
1856
<label>First Name</label>
19-
```
57+
```

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.3.1",
3+
"version": "0.4.0",
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: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010

1111
import hasAttribute from '../util/hasAttribute';
1212

13-
const errorMessage = 'img elements must have an alt tag.';
13+
const errorMessage = type => `${type} elements must have an alt tag.`;
1414

1515
module.exports = context => ({
1616
JSXOpeningElement: node => {
17-
const type = node.name.name;
18-
// Only check img tags.
19-
if (type !== 'img') {
17+
const typeCheck = [ 'img' ].concat(context.options[0]);
18+
const nodeType = node.name.name;
19+
20+
// Only check 'img' elements and custom types.
21+
if (typeCheck.indexOf(nodeType) === -1) {
2022
return;
2123
}
2224

@@ -26,12 +28,24 @@ module.exports = context => ({
2628
if (hasAltProp === false || hasAltProp === null) {
2729
context.report({
2830
node,
29-
message: errorMessage
31+
message: errorMessage(nodeType)
3032
});
3133
}
3234
}
3335
});
3436

3537
module.exports.schema = [
36-
{ type: 'object' }
38+
{
39+
"oneOf": [
40+
{ "type": "string" },
41+
{
42+
"type": "array",
43+
"items": {
44+
"type": "string"
45+
},
46+
"minItems": 1,
47+
"uniqueItems": true
48+
}
49+
]
50+
}
3751
];

src/rules/no-hash-href.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ const errorMessage = 'Links must not point to "#". Use a more descriptive href o
1414

1515
module.exports = context => ({
1616
JSXOpeningElement: node => {
17-
const type = node.name.name;
18-
// Only check img tags.
19-
if (type !== 'a') {
17+
const typeCheck = [ 'a' ].concat(context.options[0]);
18+
const nodeType = node.name.name;
19+
20+
// Only check 'a' elements and custom types.
21+
if (typeCheck.indexOf(nodeType) === -1) {
2022
return;
2123
}
2224

@@ -32,5 +34,17 @@ module.exports = context => ({
3234
});
3335

3436
module.exports.schema = [
35-
{ type: 'object' }
37+
{
38+
"oneOf": [
39+
{ "type": "string" },
40+
{
41+
"type": "array",
42+
"items": {
43+
"type": "string"
44+
},
45+
"minItems": 1,
46+
"uniqueItems": true
47+
}
48+
]
49+
}
3650
];

src/rules/redundant-alt.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ module.exports = context => ({
3131
const isVisible = isHiddenFromScreenReader(node.attributes) === false;
3232

3333
if (Boolean(altProp) && typeof altProp === 'string' && isVisible) {
34-
const hasRedundancy = REDUNDANT_WORDS.some(word => Boolean(altProp.match(new RegExp(word, 'gi'))));
34+
const hasRedundancy = REDUNDANT_WORDS
35+
.some(word => Boolean(altProp.match(new RegExp(`(?!{)${word}(?!})`, 'gi'))));
3536

3637
if (hasRedundancy === true) {
3738
context.report({

src/rules/use-label-for.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ const errorMessage = 'Form controls using a label to identify them must be ' +
1515

1616
module.exports = context => ({
1717
JSXOpeningElement: node => {
18-
const type = node.name.name;
19-
if (type !== 'label') {
18+
const typeCheck = [ 'label' ].concat(context.options[0]);
19+
const nodeType = node.name.name;
20+
21+
// Only check 'label' elements and custom types.
22+
if (typeCheck.indexOf(nodeType) === -1) {
2023
return;
2124
}
2225

@@ -32,5 +35,17 @@ module.exports = context => ({
3235
});
3336

3437
module.exports.schema = [
35-
{ type: 'object' }
38+
{
39+
"oneOf": [
40+
{ "type": "string" },
41+
{
42+
"type": "array",
43+
"items": {
44+
"type": "string"
45+
},
46+
"minItems": 1,
47+
"uniqueItems": true
48+
}
49+
]
50+
}
3651
];

0 commit comments

Comments
 (0)