Skip to content

Commit 346d70b

Browse files
author
Ethan Cohen
committed
Various bugfixes.
Fixes #8 Fixes #9 Fixes first example in #6
1 parent 6a69d99 commit 346d70b

12 files changed

+75
-36
lines changed

src/rules/img-uses-alt.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// ----------------------------------------------------------------------------
1010

1111
import hasAttribute from '../util/hasAttribute';
12+
import getAttributeValue from '../util/getAttributeValue';
1213
import getNodeType from '../util/getNodeType';
1314

1415
const errorMessage = type => `${type} elements must have an alt tag.`;
@@ -24,9 +25,11 @@ module.exports = context => ({
2425
}
2526

2627
const hasAltProp = hasAttribute(node.attributes, 'alt');
28+
const altProp = hasAltProp ? getAttributeValue(hasAltProp) : undefined;
29+
const isInvalid = hasAltProp === false || Boolean(altProp) === false;
2730

2831
// alt must have a value.
29-
if (hasAltProp === false || hasAltProp === null) {
32+
if (isInvalid) {
3033
context.report({
3134
node,
3235
message: errorMessage(nodeType)

src/rules/label-uses-for.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// ----------------------------------------------------------------------------
1010

1111
import hasAttribute from '../util/hasAttribute';
12+
import getAttributeValue from '../util/getAttributeValue';
1213
import getNodeType from '../util/getNodeType';
1314

1415
const errorMessage = 'Form controls using a label to identify them must be ' +
@@ -24,9 +25,11 @@ module.exports = context => ({
2425
return;
2526
}
2627

27-
const hasHtmlForAttr = hasAttribute(node.attributes, 'htmlFor');
28+
const htmlForAttr = hasAttribute(node.attributes, 'htmlFor');
29+
const htmlForValue = getAttributeValue(htmlForAttr);
30+
const isInvalid = htmlForAttr === false || htmlForValue === null || htmlForValue === undefined;
2831

29-
if (hasHtmlForAttr === false) {
32+
if (isInvalid) {
3033
context.report({
3134
node,
3235
message: errorMessage

src/rules/mouse-events-map-to-key-events.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// ----------------------------------------------------------------------------
1111

1212
import hasAttribute from '../util/hasAttribute';
13+
import getAttributeValue from '../util/getAttributeValue';
1314

1415
const mouseOverErrorMessage = 'onMouseOver must be accompanied by onFocus for accessibility.';
1516
const mouseOutErrorMessage = 'onMouseOut must be accompanied by onBlur for accessibility.';
@@ -20,9 +21,13 @@ module.exports = context => ({
2021

2122
// Check onmouseover / onfocus pairing.
2223
const hasOnMouseOver = hasAttribute(attributes, 'onMouseOver');
23-
if (Boolean(hasOnMouseOver) === true) {
24+
const onMouseOverValue = getAttributeValue(hasOnMouseOver);
25+
26+
if (Boolean(hasOnMouseOver) === true && (onMouseOverValue !== null || onMouseOverValue !== undefined)) {
2427
const hasOnFocus = hasAttribute(attributes, 'onFocus');
25-
if (hasOnFocus === false) {
28+
const onFocusValue = getAttributeValue(hasOnFocus);
29+
30+
if (hasOnFocus === false || onFocusValue === null || onFocusValue === undefined) {
2631
context.report({
2732
node,
2833
message: mouseOverErrorMessage
@@ -32,9 +37,12 @@ module.exports = context => ({
3237

3338
// Checkout onmouseout / onblur pairing
3439
const hasOnMouseOut = hasAttribute(attributes, 'onMouseOut');
35-
if (Boolean(hasOnMouseOut) === true) {
40+
const onMouseOutValue = getAttributeValue(hasOnMouseOut);
41+
if (Boolean(hasOnMouseOut) === true && (onMouseOutValue !== null || onMouseOutValue !== undefined)) {
3642
const hasOnBlur = hasAttribute(attributes, 'onBlur');
37-
if (hasOnBlur === false) {
43+
const onBlurValue = getAttributeValue(hasOnBlur);
44+
45+
if (hasOnBlur === false || onBlurValue === null || onBlurValue === undefined) {
3846
context.report({
3947
node,
4048
message: mouseOutErrorMessage

src/rules/no-access-key.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@
99
// ----------------------------------------------------------------------------
1010

1111
import hasAttribute from '../util/hasAttribute';
12+
import getAttributeValue from '../util/getAttributeValue';
1213

13-
const errorMessage = 'No access key attribute allowed. Incosistencies ' +
14+
const errorMessage = 'No access key attribute allowed. Inconsistencies ' +
1415
'between keyboard shortcuts and keyboard comments used by screenreader ' +
1516
'and keyboard only users create a11y complications.';
1617

1718
module.exports = context => ({
1819
JSXOpeningElement: node => {
1920
const hasAccessKey = hasAttribute(node.attributes, 'accesskey');
21+
const accessKeyValue = getAttributeValue(hasAccessKey);
2022

21-
if (Boolean(hasAccessKey) === true) {
23+
if (Boolean(hasAccessKey) === true && Boolean(accessKeyValue) === true) {
2224
context.report({
2325
node,
2426
message: errorMessage

src/rules/no-hash-href.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// ----------------------------------------------------------------------------
1010

1111
import hasAttribute from '../util/hasAttribute';
12+
import getAttributeValue from '../util/getAttributeValue';
1213
import getNodeType from '../util/getNodeType';
1314

1415
const errorMessage = 'Links must not point to "#". Use a more descriptive href or use a button instead.';
@@ -24,8 +25,9 @@ module.exports = context => ({
2425
}
2526

2627
const href = hasAttribute(node.attributes, 'href');
28+
const value = getAttributeValue(href);
2729

28-
if (href === '#') {
30+
if (href && value === '#') {
2931
context.report({
3032
node,
3133
message: errorMessage

src/rules/onclick-uses-role.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader';
99
import isInteractiveElement from '../util/isInteractiveElement';
1010
import hasAttribute from '../util/hasAttribute';
11+
import getAttributeValue from '../util/getAttributeValue';
1112
import getNodeType from '../util/getNodeType';
1213

1314
// ----------------------------------------------------------------------------
@@ -26,7 +27,8 @@ module.exports = context => ({
2627

2728
const isVisible = isHiddenFromScreenReader(attributes) === false;
2829
const isNonInteractive = isInteractiveElement(getNodeType(node), attributes) === false;
29-
const noRoleAttribute = hasAttribute(attributes, 'role') === false;
30+
const roleAttribute = hasAttribute(attributes, 'role');
31+
const noRoleAttribute = roleAttribute === false || Boolean(getAttributeValue(roleAttribute)) === false;
3032

3133
// Visible, non-interactive elements require role attribute.
3234
if (isVisible && isNonInteractive && noRoleAttribute) {

src/rules/redundant-alt.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// ----------------------------------------------------------------------------
1010

1111
import hasAttribute from '../util/hasAttribute';
12+
import getAttributeValue from '../util/getAttributeValue';
1213
import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader';
1314
import getNodeType from '../util/getNodeType';
1415

@@ -21,6 +22,11 @@ const REDUNDANT_WORDS = [
2122
const errorMessage = 'Redundant alt attribute. Screen-readers already announce `img` tags as an image. ' +
2223
'You don\'t need to use the words `image`, `photo,` or `picture` in the alt prop.';
2324

25+
const validTypes = [
26+
'LITERAL',
27+
'TEMPLATELITERAL'
28+
];
29+
2430
module.exports = context => ({
2531
JSXOpeningElement: node => {
2632
const type = getNodeType(node);
@@ -29,11 +35,26 @@ module.exports = context => ({
2935
}
3036

3137
const altProp = hasAttribute(node.attributes, 'alt');
38+
// Return if alt prop is not present.
39+
if (altProp === false) {
40+
return;
41+
}
42+
43+
// Only check literals, as we should not enforce variable names :P
44+
const normalizedType = altProp.value && altProp.value.type.toUpperCase() === 'JSXEXPRESSIONCONTAINER' ?
45+
altProp.value.expression.type.toUpperCase() :
46+
altProp.value.type.toUpperCase();
47+
48+
if (validTypes.indexOf(normalizedType) === -1) {
49+
return;
50+
}
51+
52+
const value = getAttributeValue(altProp);
3253
const isVisible = isHiddenFromScreenReader(node.attributes) === false;
3354

34-
if (Boolean(altProp) && typeof altProp === 'string' && isVisible) {
55+
if (Boolean(value) && typeof value === 'string' && isVisible) {
3556
const hasRedundancy = REDUNDANT_WORDS
36-
.some(word => Boolean(altProp.match(new RegExp(`(?!{)${word}(?!})`, 'gi'))));
57+
.some(word => Boolean(value.match(new RegExp(`(?!{)${word}(?!})`, 'gi'))));
3758

3859
if (hasRedundancy === true) {
3960
context.report({

src/util/hasAttribute.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
'use strict';
22

3-
import getAttributeValue from './getAttributeValue';
43

54
/**
6-
* Returns the value of the attribute or false, indicating the attribute
7-
* is not present on the JSX opening element. This skips over spread attributes
5+
* Returns the JSXAttribute itself or false, indicating the attribute
6+
* is not present on the JSXOpeningElement. This skips over spread attributes
87
* as the purpose of this linter is to do hard checks of explicit JSX props.
98
*
10-
* This treats undefined values as missing props, as they will not be used for
11-
* rendering on elements that live closest to the DOM (pure html JSX elements).
129
*/
1310
const hasAttribute = (attributes, attribute) => {
14-
let value = false;
11+
let nodeAttribute = undefined;
1512

1613
const hasAttr = attributes.some(attr => {
1714
// If the attributes contain a spread attribute, then skip.
@@ -21,16 +18,14 @@ const hasAttribute = (attributes, attribute) => {
2118

2219
// Normalize.
2320
if (attr.name.name.toUpperCase() === attribute.toUpperCase()) {
24-
value = getAttributeValue(attr);
25-
26-
// If the value is undefined, it doesn't really have the attribute.
27-
return value !== undefined;
21+
nodeAttribute = attr;
22+
return true;
2823
}
2924

3025
return false;
3126
});
3227

33-
return hasAttr ? value : false;
28+
return hasAttr ? nodeAttribute : false;
3429
};
3530

3631
export default hasAttribute;

src/util/isHiddenFromScreenReader.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
import hasAttribute from './hasAttribute';
4+
import getAttributeValue from './getAttributeValue';
45

56
/**
67
* Returns boolean indicating that the aria-hidden prop
@@ -9,8 +10,8 @@ import hasAttribute from './hasAttribute';
910
* <div aria-hidden /> is equivalent to the DOM as <div aria-hidden=true />.
1011
*/
1112
const isHiddenFromScreenReader = attributes => {
12-
const hasAriaHidden = hasAttribute(attributes, 'aria-hidden');
13-
return hasAriaHidden && (hasAriaHidden === true || hasAriaHidden === null);
13+
const ariaHidden = getAttributeValue(hasAttribute(attributes, 'aria-hidden'));
14+
return ariaHidden === true || ariaHidden === null;
1415
};
1516

1617
export default isHiddenFromScreenReader;

src/util/isInteractiveElement.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
import hasAttribute from './hasAttribute';
4+
import getAttributeValue from './getAttributeValue';
45

56
const interactiveMap = {
67
a: attributes => {
@@ -10,8 +11,8 @@ const interactiveMap = {
1011
},
1112
button: () => true,
1213
input: attributes => {
13-
const hasTypeAttr = hasAttribute(attributes, 'type');
14-
return hasTypeAttr ? hasTypeAttr.toUpperCase() !== 'HIDDEN' : true;
14+
const typeAttr = getAttributeValue(hasAttribute(attributes, 'type'));
15+
return typeAttr ? typeAttr.toUpperCase() !== 'HIDDEN' : true;
1516
},
1617
option: () => true,
1718
select: () => true,

0 commit comments

Comments
 (0)