Skip to content

Commit

Permalink
Add no-hash-href rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ethan Cohen committed Mar 2, 2016
1 parent a2ab4ef commit 1f42630
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 7 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
Static AST checker for accessibility rules on JSX elements.

## Why?
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.
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.

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.

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

## License

Expand Down
21 changes: 21 additions & 0 deletions docs/rules/no-hash-href.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# no-hash-href

Enforce an anchor element's href prop value is not just #. You should use something more descriptive, or use a button instead.

## Rule details

This rule takes no arguments.

### Succeed
```jsx
<a href="https://github.com" />
<a href="#section" />
<a href="foo" />
```

### Fail
```jsx
<a href="#" />
<a href={"#"} />
<a href={`#`} />
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-jsx-a11y",
"version": "0.2.3",
"version": "0.3.0",
"description": "A static analysis linter of jsx and their accessibility with screen readers.",
"keywords": [
"eslint",
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module.exports = {
'mouse-events-map-to-key-events': require('./rules/mouse-events-map-to-key-events'),
'use-onblur-not-onchange': require('./rules/use-onblur-not-onchange'),
'no-access-key': require('./rules/no-access-key'),
'use-label-for': require('./rules/use-label-for')
'use-label-for': require('./rules/use-label-for'),
'no-hash-href': require('./rules/no-hash-href')
},
configs: {
recommended: {
Expand Down
36 changes: 36 additions & 0 deletions src/rules/no-hash-href.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @fileoverview Enforce links may not point to just #.
* @author Ethan Cohen
*/
'use strict';

// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------

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

const errorMessage = 'Links must not point to "#". Use a more descriptive href or use a button instead.';

module.exports = context => ({
JSXOpeningElement: node => {
const type = node.name.name;
// Only check img tags.
if (type !== 'a') {
return;
}

const href = hasAttribute(node.attributes, 'href');

if (href === '#') {
context.report({
node,
message: errorMessage
});
}
}
});

module.exports.schema = [
{ type: 'object' }
];
4 changes: 2 additions & 2 deletions src/util/buildTemplateLiteral.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ const buildTemplateLiteral = templateLiteral => {
if (type === 'TemplateElement') {
return raw + part.value.raw;
} else if (type === 'Identifier') {
return raw + part.name;
return part.name === 'undefined' ? raw : raw + part.name;
}

return raw;
}, '');

return rawString === "undefined" ? undefined : rawString;
return rawString === '' ? undefined : rawString;
};

export default buildTemplateLiteral;
4 changes: 3 additions & 1 deletion tests/src/rules/no-access-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ ruleTester.run('no-access-key', rule, {
{ code: '<div />;', parserOptions },
{ code: '<div {...props} />', parserOptions },
{ code: '<div accessKey={undefined} />', parserOptions },
{ code: '<div accessKey={`${undefined}`} />', parserOptions }
{ code: '<div accessKey={`${undefined}`} />', parserOptions },
{ code: '<div accessKey={`${undefined}${undefined}`} />', parserOptions }
],
invalid: [
{ code: '<div accesskey="h" />', errors: [ expectedError ], parserOptions },
Expand All @@ -46,6 +47,7 @@ ruleTester.run('no-access-key', rule, {
{ code: '<div acCesSKeY="y" />', errors: [ expectedError ], parserOptions },
{ code: '<div accessKey={"y"} />', errors: [ expectedError ], parserOptions },
{ code: '<div accessKey={`${y}`} />', errors: [ expectedError ], parserOptions },
{ code: '<div accessKey={`${undefined}y${undefined}`} />', errors: [ expectedError ], parserOptions },
{ code: '<div accessKey={`This is ${bad}`} />', errors: [ expectedError ], parserOptions },
{ code: '<div accessKey={accessKey} />', errors: [ expectedError ], parserOptions }
]
Expand Down
53 changes: 53 additions & 0 deletions tests/src/rules/no-hash-href.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @fileoverview Enforce links may not point to just #.
* @author Ethan Cohen
*/

'use strict';

// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------

import rule from '../../../src/rules/no-hash-href';
import { RuleTester } from 'eslint';

const parserOptions = {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true
}
};

// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------

const ruleTester = new RuleTester();

const expectedError = {
message: 'Links must not point to "#". Use a more descriptive href or use a button instead.',
type: 'JSXOpeningElement'
};

ruleTester.run('no-hash-href', rule, {
valid: [
{ code: '<a />;', parserOptions },
{ code: '<a {...props} />', parserOptions },
{ code: '<a href="foo" />', parserOptions },
{ code: '<a href={foo} />', parserOptions },
{ code: '<a href="/foo" />', parserOptions },
{ code: '<a href={`${undefined}`} />', parserOptions },
{ code: '<div href="foo" />', parserOptions },
{ code: '<a href={`${undefined}foo`}/>', parserOptions },
{ code: '<a href={`#${undefined}foo`}/>', parserOptions },
{ code: '<a href={`#foo`}/>', parserOptions },
{ code: '<a href={"foo"}/>', parserOptions },
{ code: '<a href="#foo" />', parserOptions }
],
invalid: [
{ code: '<a href="#" />', errors: [ expectedError ], parserOptions },
{ code: '<a href={"#"} />', errors: [ expectedError ], parserOptions },
{ code: '<a href={`#${undefined}`} />', errors: [ expectedError ], parserOptions }
]
});
5 changes: 4 additions & 1 deletion tests/src/rules/redundant-alt.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ ruleTester.run('redundant-alt', rule, {
{ code: '<img alt="image of cool person" aria-hidden={false} />', errors: [ expectedError ], parserOptions },
{ code: '<img alt="photo" {...this.props} />', errors: [ expectedError ], parserOptions },
{ code: '<img alt="image" {...this.props} />', errors: [ expectedError ], parserOptions },
{ code: '<img alt="picture" {...this.props} />', errors: [ expectedError ], parserOptions }
{ code: '<img alt="picture" {...this.props} />', errors: [ expectedError ], parserOptions },
{ code: '<img alt="{`picture doing ${things}`}" {...this.props} />', errors: [ expectedError ], parserOptions },
{ code: '<img alt="{`photo doing ${things}`}" {...this.props} />', errors: [ expectedError ], parserOptions },
{ code: '<img alt="{`image doing ${things}`}" {...this.props} />', errors: [ expectedError ], parserOptions }
]
});

0 comments on commit 1f42630

Please sign in to comment.