Skip to content

Commit 1f42630

Browse files
author
Ethan Cohen
committed
Add no-hash-href rule.
1 parent a2ab4ef commit 1f42630

File tree

9 files changed

+124
-7
lines changed

9 files changed

+124
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
Static AST checker for accessibility rules on JSX elements.
2222

2323
## Why?
24-
Ryan Florence built out this awesome runtime-analysis tool called [react-a11y](https://github.com/reactjs/react-a11y). It is pretty awesome. However, this creates more package-bloat and requries initialization in your code. Since you're probably already using linting in your project, this plugin comes for free and closer to actual development. Pairing this plugin with an editor lint plugin, you can bake accessibility standards into your application in real-time.
24+
Ryan Florence built out this awesome runtime-analysis tool called [react-a11y](https://github.com/reactjs/react-a11y). It is pretty awesome. However, this creates more package-bloat and requries initialization in your code. Since you're probably already using linting in your project, this plugin comes for free and closer to actual development. Pairing this plugin with an editor lint plugin, you can bake accessibility standards into your application in real-time.
2525

2626
Note: This project does not *replace* react-a11y, but can and should be used in conjunction with it. Static analysis tools cannot determine values of variables that are being placed in props before runtime, so linting will not fail if that value is undefined and/or does not pass the lint rule.
2727

@@ -73,6 +73,7 @@ Then configure the rules you want to use under the rules section.
7373
- [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.
7474
- [use-label-for](docs/rules/use-label-for.md): Enforce that label elements have the htmlFor attribute
7575
- [redundant-alt](docs/rules/redundant-alt.md): Enforce img alt attribute does not contain the word image, picture, or photo.
76+
- [no-hash-href](docs/rules/no-hash-href.md): Enforce an anchor element's href prop value is not just #.
7677

7778
## License
7879

docs/rules/no-hash-href.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# no-hash-href
2+
3+
Enforce an anchor element's href prop value is not just #. You should use something more descriptive, or use a button instead.
4+
5+
## Rule details
6+
7+
This rule takes no arguments.
8+
9+
### Succeed
10+
```jsx
11+
<a href="https://github.com" />
12+
<a href="#section" />
13+
<a href="foo" />
14+
```
15+
16+
### Fail
17+
```jsx
18+
<a href="#" />
19+
<a href={"#"} />
20+
<a href={`#`} />
21+
```

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

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ module.exports = {
88
'mouse-events-map-to-key-events': require('./rules/mouse-events-map-to-key-events'),
99
'use-onblur-not-onchange': require('./rules/use-onblur-not-onchange'),
1010
'no-access-key': require('./rules/no-access-key'),
11-
'use-label-for': require('./rules/use-label-for')
11+
'use-label-for': require('./rules/use-label-for'),
12+
'no-hash-href': require('./rules/no-hash-href')
1213
},
1314
configs: {
1415
recommended: {

src/rules/no-hash-href.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @fileoverview Enforce links may not point to just #.
3+
* @author Ethan Cohen
4+
*/
5+
'use strict';
6+
7+
// ----------------------------------------------------------------------------
8+
// Rule Definition
9+
// ----------------------------------------------------------------------------
10+
11+
import hasAttribute from '../util/hasAttribute';
12+
13+
const errorMessage = 'Links must not point to "#". Use a more descriptive href or use a button instead.';
14+
15+
module.exports = context => ({
16+
JSXOpeningElement: node => {
17+
const type = node.name.name;
18+
// Only check img tags.
19+
if (type !== 'a') {
20+
return;
21+
}
22+
23+
const href = hasAttribute(node.attributes, 'href');
24+
25+
if (href === '#') {
26+
context.report({
27+
node,
28+
message: errorMessage
29+
});
30+
}
31+
}
32+
});
33+
34+
module.exports.schema = [
35+
{ type: 'object' }
36+
];

src/util/buildTemplateLiteral.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ const buildTemplateLiteral = templateLiteral => {
2222
if (type === 'TemplateElement') {
2323
return raw + part.value.raw;
2424
} else if (type === 'Identifier') {
25-
return raw + part.name;
25+
return part.name === 'undefined' ? raw : raw + part.name;
2626
}
2727

2828
return raw;
2929
}, '');
3030

31-
return rawString === "undefined" ? undefined : rawString;
31+
return rawString === '' ? undefined : rawString;
3232
};
3333

3434
export default buildTemplateLiteral;

tests/src/rules/no-access-key.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ ruleTester.run('no-access-key', rule, {
3737
{ code: '<div />;', parserOptions },
3838
{ code: '<div {...props} />', parserOptions },
3939
{ code: '<div accessKey={undefined} />', parserOptions },
40-
{ code: '<div accessKey={`${undefined}`} />', parserOptions }
40+
{ code: '<div accessKey={`${undefined}`} />', parserOptions },
41+
{ code: '<div accessKey={`${undefined}${undefined}`} />', parserOptions }
4142
],
4243
invalid: [
4344
{ code: '<div accesskey="h" />', errors: [ expectedError ], parserOptions },
@@ -46,6 +47,7 @@ ruleTester.run('no-access-key', rule, {
4647
{ code: '<div acCesSKeY="y" />', errors: [ expectedError ], parserOptions },
4748
{ code: '<div accessKey={"y"} />', errors: [ expectedError ], parserOptions },
4849
{ code: '<div accessKey={`${y}`} />', errors: [ expectedError ], parserOptions },
50+
{ code: '<div accessKey={`${undefined}y${undefined}`} />', errors: [ expectedError ], parserOptions },
4951
{ code: '<div accessKey={`This is ${bad}`} />', errors: [ expectedError ], parserOptions },
5052
{ code: '<div accessKey={accessKey} />', errors: [ expectedError ], parserOptions }
5153
]

tests/src/rules/no-hash-href.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @fileoverview Enforce links may not point to just #.
3+
* @author Ethan Cohen
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
import rule from '../../../src/rules/no-hash-href';
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: 'Links must not point to "#". Use a more descriptive href or use a button instead.',
30+
type: 'JSXOpeningElement'
31+
};
32+
33+
ruleTester.run('no-hash-href', rule, {
34+
valid: [
35+
{ code: '<a />;', parserOptions },
36+
{ code: '<a {...props} />', parserOptions },
37+
{ code: '<a href="foo" />', parserOptions },
38+
{ code: '<a href={foo} />', parserOptions },
39+
{ code: '<a href="/foo" />', parserOptions },
40+
{ code: '<a href={`${undefined}`} />', parserOptions },
41+
{ code: '<div href="foo" />', parserOptions },
42+
{ code: '<a href={`${undefined}foo`}/>', parserOptions },
43+
{ code: '<a href={`#${undefined}foo`}/>', parserOptions },
44+
{ code: '<a href={`#foo`}/>', parserOptions },
45+
{ code: '<a href={"foo"}/>', parserOptions },
46+
{ code: '<a href="#foo" />', parserOptions }
47+
],
48+
invalid: [
49+
{ code: '<a href="#" />', errors: [ expectedError ], parserOptions },
50+
{ code: '<a href={"#"} />', errors: [ expectedError ], parserOptions },
51+
{ code: '<a href={`#${undefined}`} />', errors: [ expectedError ], parserOptions }
52+
]
53+
});

tests/src/rules/redundant-alt.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ ruleTester.run('redundant-alt', rule, {
6363
{ code: '<img alt="image of cool person" aria-hidden={false} />', errors: [ expectedError ], parserOptions },
6464
{ code: '<img alt="photo" {...this.props} />', errors: [ expectedError ], parserOptions },
6565
{ code: '<img alt="image" {...this.props} />', errors: [ expectedError ], parserOptions },
66-
{ code: '<img alt="picture" {...this.props} />', errors: [ expectedError ], parserOptions }
66+
{ code: '<img alt="picture" {...this.props} />', errors: [ expectedError ], parserOptions },
67+
{ code: '<img alt="{`picture doing ${things}`}" {...this.props} />', errors: [ expectedError ], parserOptions },
68+
{ code: '<img alt="{`photo doing ${things}`}" {...this.props} />', errors: [ expectedError ], parserOptions },
69+
{ code: '<img alt="{`image doing ${things}`}" {...this.props} />', errors: [ expectedError ], parserOptions }
6770
]
6871
});

0 commit comments

Comments
 (0)