Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
[Issue #218] react-a11y-img-has-alt - image with alt should not have …
Browse files Browse the repository at this point in the history
…presentational role

closes #218
closes #260
  • Loading branch information
t-ligu authored and HamletDRC committed Sep 20, 2016
1 parent 010ea0d commit e690958
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 93 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Rule Name | Description | Since
`react-a11y-aria-unsupported-elements` | For accessibility of your website, enforce that elements that do not support ARIA roles, states, and properties do not have those attributes. | 2.0.11
`react-a11y-event-has-role` | For accessibility of your website, Elements with event handlers must have explicit role or implicit role.<br/>References:<br/>[WCAG Rule 94](http://oaa-accessibility.org/wcag20/rule/94/)<br/>[Using the button role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role) | 2.0.11
`react-a11y-image-button-has-alt` | For accessibility of your website, enforce that inputs element with `type="image"` must have non-empty alt attribute. | 2.0.11
`react-a11y-img-has-alt` | Enforce that an `img` element contains the `alt` attribute or `role='presentation'` for decorative image. All images must have `alt` text to convey their purpose and meaning to **screen reader users**. Besides, the `alt` attribute specifies an alternate text for an image, if the image cannot be displayed. This rule accepts as a parameter a string array for tag names other than img to also check. For example, if you use a custom tag named 'Image' then configure the rule with: `[true, ['Image']]`<br/>References:<br/>[Web Content Accessibility Guidelines 1.0](https://www.w3.org/TR/WCAG10/wai-pageauth.html#tech-text-equivalent)<br/>[ARIA Presentation Role](https://www.w3.org/TR/wai-aria/roles#presentation) | 2.0.11
`react-a11y-img-has-alt` | Enforce that an `img` element contains the `alt` attribute or `role='presentation'` for a decorative image. All images must have `alt` text to convey their purpose and meaning to **screen reader users**. Besides, the `alt` attribute specifies an alternate text for an image, if the image cannot be displayed. This rule accepts as a parameter a string array for tag names other than img to also check. For example, if you use a custom tag named 'Image' then configure the rule with: `[true, ['Image']]`<br/>References:<br/>[Web Content Accessibility Guidelines 1.0](https://www.w3.org/TR/WCAG10/wai-pageauth.html#tech-text-equivalent)<br/>[ARIA Presentation Role](https://www.w3.org/TR/wai-aria/roles#presentation)<br/>[WCAG Rule 31: If an image has an alt or title attribute, it should not have a presentation role](http://oaa-accessibility.org/wcag20/rule/31/) | 2.0.11
`react-a11y-lang` | For accessibility of your website, HTML elements must have a lang attribute and the attribute must be a valid language code.<br/>References:<br/>* [H58: Using language attributes to identify changes in the human language](https://www.w3.org/TR/WCAG20-TECHS/H58.html)<br/>* [lang attribute must have a valid value](https://dequeuniversity.com/rules/axe/1.1/valid-lang)<br/>[List of ISO 639-1 codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) | 2.0.11
`react-a11y-anchors` | For accessibility of your website, anchor element link text should be at least 4 characters long. Links with the same HREF should have the same link text. Links that point to different HREFs should have different link text. Links with images and text content, the alt attribute should be unique to the text content or empty. An an anchor element's href prop value must not be just #. <br/>References:<br/>[WCAG Rule 38: Link text should be as least four 4 characters long](http://oaa-accessibility.org/wcag20/rule/38/)<br/>[WCAG Rule 39: Links with the same HREF should have the same link text](http://oaa-accessibility.org/wcag20/rule/39/)<br/>[WCAG Rule 41: Links that point to different HREFs should have different link text](http://oaa-accessibility.org/wcag20/rule/41/)<br/>[WCAG Rule 43: Links with images and text content, the alt attribute should be unique to the text content or empty](http://oaa-accessibility.org/wcag20/rule/43/)<br/> | 2.0.11
`react-a11y-meta` | For accessibility of your website, HTML meta elements must not have http-equiv="refresh". | 2.0.11
Expand Down
74 changes: 46 additions & 28 deletions src/reactA11yImgHasAltRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@ import {
} from './utils/JsxAttribute';
import { isJsxSpreadAttribute } from './utils/TypeGuard';

const roleString: string = 'role';
const altString: string = 'alt';
const ROLE_STRING: string = 'role';
const ALT_STRING: string = 'alt';

export function getFailureStringNoAlt(tagName: string): string {
return `<${tagName}> elements must have an alt attribute or use role='presentation' for presentational images. \
return `<${tagName}> elements must have an non-empty alt attribute or \
use empty alt attribute and role='presentation' for presentational images. \
A reference for the presentation role can be found at https://www.w3.org/TR/wai-aria/roles#presentation.`;
}

export function getFailureStringEmptyAlt(tagName: string): string {
return `The value of 'alt' attribute in <${tagName}> tag is undefined or empty. \
Add more details in 'alt' attribute or use role='presentation' for presentational images. \
A reference for the presentation role can be found at https://www.w3.org/TR/wai-aria/roles#presentation.`;
export function getFailureStringEmptyAltAndNotPresentationRole(tagName: string): string {
return `The value of alt attribute in <${tagName}> tag is empty and role value is not presentation. \
Add more details in alt attribute or specify role attribute to equal 'presentation' when 'alt' attribute is empty.`;
}

export function getFailureStringNonEmptyAltAndPresentationRole(tagName: string): string {
return `The value of alt attribute in <${tagName}> tag is non-empty and role value is presentation. \
Remove role='presentation' or specify 'alt' attributeto be empty when role attributes equals 'presentation'.`;
}

/**
Expand All @@ -31,7 +36,8 @@ export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'react-a11y-img-has-alt',
type: 'maintainability',
description: 'Enforce that an `img` element contains the `alt` attribute or `role="presentation"` for decorative image.',
description: 'Enforce that an img element contains the non-empty alt attribute. ' +
'For decorative images, using empty alt attribute and role="presentation".',
options: 'string[]',
optionExamples: ['true', '[true, ["Image"]]'],
issueClass: 'Non-SDL',
Expand All @@ -49,9 +55,19 @@ export class Rule extends Lint.Rules.AbstractRule {
}

class ImgHasAltWalker extends Lint.RuleWalker {
public visitJsxElement(node: ts.JsxElement): void {
this.checkJsxOpeningElement(node.openingElement);
super.visitJsxElement(node);
}

public visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
this.checkJsxOpeningElement(node);
super.visitJsxSelfClosingElement(node);
}

private checkJsxOpeningElement(node: ts.JsxOpeningElement): void {
// Tag name is sensitive on lowercase or uppercase, we shoudn't normalize tag names in this rule.
const tagName: string = node.tagName && node.tagName.getText();
const tagName: string = node.tagName.getText();
const options: any[] = this.getOptions(); // tslint:disable-line:no-any

// The additionalTagNames are specified by tslint config to check not only 'img' tag but also customized tag.
Expand All @@ -71,31 +87,33 @@ class ImgHasAltWalker extends Lint.RuleWalker {
}

const attributes: { [propName: string]: ts.JsxAttribute } = getJsxAttributesFromJsxElement(node);
const role: ts.JsxAttribute = attributes[roleString];
const roleValue: string = role && getStringLiteral(role);
const altAttribute: ts.JsxAttribute = attributes[ALT_STRING];

// if <img> element has role of 'presentation', it's presentational image, don't check it;
// @example <img role='presentation' />
if (roleValue && roleValue.match(/\bpresentation\b/)) {
return;
}

const altProp: ts.JsxAttribute = attributes[altString];

if (!altProp) {
if (!altAttribute) {
this.addFailure(this.createFailure(
node.getStart(),
node.getWidth(),
getFailureStringNoAlt(tagName)
));
} else if (isEmpty(altProp) || getStringLiteral(altProp) === '') {
this.addFailure(this.createFailure(
altProp.getStart(),
altProp.getWidth(),
getFailureStringEmptyAlt(tagName)
));
} else {
const roleAttribute: ts.JsxAttribute = attributes[ROLE_STRING];
const roleAttributeValue: string = roleAttribute ? getStringLiteral(roleAttribute) : '';
const isPresentationRole: boolean = !!roleAttributeValue.toLowerCase().match(/\bpresentation\b/);
const isEmptyAlt: boolean = isEmpty(altAttribute) || getStringLiteral(altAttribute) === '';

if (!isEmptyAlt && isPresentationRole) { // <img alt='altValue' role='presentation' />
this.addFailure(this.createFailure(
node.getStart(),
node.getWidth(),
getFailureStringNonEmptyAltAndPresentationRole(tagName)
));
} else if (isEmptyAlt && !isPresentationRole) { // <img alt='' />
this.addFailure(this.createFailure(
node.getStart(),
node.getWidth(),
getFailureStringEmptyAltAndNotPresentationRole(tagName)
));
}
}

super.visitJsxSelfClosingElement(node);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React = require('react');
let Picture;
const a = <Picture alt='' />
const b = <Picture alt role='button img' />
const c = <Picture alt={''} />
const c = <Picture alt={''} role='button' />
const d = <img alt='' />
const e = <img aLt />
const f = <img alt={''} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React = require('react');

let Picture;
let altValue;

const a = <Picture alt='altValue' role='presentation' />
const b = <Picture alt={ 'altValue' } role={ 'presentation' } />
const c = <Picture alt={ altValue } role='Presentation' />
const d = <img alt='altValue' role='presentation button' />
const e = <img aLt='altValue' role='presentation img' />
const f = <img alt={ altValue } role='presentation' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React = require('react');

let Picture;

const a = <Picture role='presentation' alt />
const b = <Picture role={'presentation'} alt='' />
const c = <Picture role='button presentation' alt={undefined} />
const d = <img role='presentation' alt={ null } />
const e = <img role={'presentation'} alt={ '' } />
const f = <img role='button presentation' alt />
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React = require('react');

let Picture, validAltValue;

const a = <Picture alt='validAltValue' />
const b = <Picture alt={validAltValue} />
const a = <Picture alt='validAltValue' role='button' />
const b = <Picture alt={validAltValue} role='link' />
const c = <Picture alt={'validAltValue'} />
const d = <img alt='validAltValue' />
const d = <img alt='validAltValue' role='button link' />
const e = <img aLt={validAltValue} />
const f = <img alt={'validAltValue'} />

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React = require('react');

const a = <img alt />
const a = <img alt role='button'/>
const b = <img Alt='' />
const c = <img ALT={undefined} />
const d = <img alt={''} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React = require('react');

let altValue;

const a = <img alt='altValue' role='presentation' />
const b = <img Alt='altValue' role='presentation button' />
const c = <img ALT={ altValue } role={ 'presentation' } />
const d = <img alt={'altValue'} role='Presentation' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React = require('react');

const a = <img role='presentation' alt />
const b = <img role='presentation' alt='' />
const c = <img role='button presentation' alt={ undefined } />
const d = <img role={'presentation button'} alt={''} />

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React = require('react');

const a = <img />
const b = <img alt=''/>
let Img;
let IMG;

const a = <Img />
const b = <IMG alt=''/>
2 changes: 1 addition & 1 deletion tests/ReactA11yProptypesRuleTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('reactA11yProptypesRule', () => {
const fileDirectory: string = 'test-data/ReactA11yProptypes/PassingTestInputs/';

it('when can not check the type of attribute value until running time', () => {
const fileName: string = fileDirectory + 'canNotCheckUtilRunTime';
const fileName: string = fileDirectory + 'canNotCheckUntilRunTime.tsx';
TestHelper.assertNoViolation(ruleName, fileName);
});

Expand Down
Loading

0 comments on commit e690958

Please sign in to comment.