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

Commit

Permalink
Fix #275 Add new rule for accessible input elements (#547)
Browse files Browse the repository at this point in the history
* Add new rule of react-a11y-input-elements

* Update README.md and build new rule

* Use getJsxAttributesFromJsxElement only for input elements

* Revert tslint.json formator changes

* Fix missing revert tslint changes

* Update rule and tests with latest master changes
  • Loading branch information
br1anchen authored and Josh Goldberg committed Oct 18, 2018
1 parent 43b6b3c commit 894dc6b
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Rule Name | Description | Since
`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 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-input-elements` | For accessibility of your website, HTML input boxes and text areas must include default, place-holding characters.<br/>References:<br/>* [WCAG 10.4](https://www.w3.org/TR/WAI-WEBCONTENT-TECHS/#tech-place-holders)<br/>[WCAG 11.5](https://www.w3.org/TR/WCAG10-HTML-TECHS/#forms-specific) | 5.2.3
`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-meta` | For accessibility of your website, HTML meta elements must not have http-equiv="refresh". | 2.0.11
`react-a11y-no-onchange` | For accessibility of your website, enforce usage of onBlur over onChange on select menus. | 5.2.3
Expand Down
1 change: 1 addition & 0 deletions recommended_ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ module.exports = {
"react-a11y-event-has-role": true,
"react-a11y-image-button-has-alt": true,
"react-a11y-img-has-alt": true,
"react-a11y-input-elements": true,
"react-a11y-lang": true,
"react-a11y-meta": true,
"react-a11y-no-onchange": true,
Expand Down
71 changes: 71 additions & 0 deletions src/reactA11yInputElementsRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as ts from 'typescript';
import * as Lint from 'tslint';

import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker';
import {getJsxAttributesFromJsxElement, isEmpty} from './utils/JsxAttribute';
import {ExtendedMetadata} from './utils/ExtendedMetadata';

export const MISSING_PLACEHOLDER_INPUT_FAILURE_STRING: string
= 'Input elements must include default, place-holding characters if empty';
export const MISSING_PLACEHOLDER_TEXTAREA_FAILURE_STRING: string
= 'Textarea elements must include default, place-holding characters if empty';

/**
* Implementation of the react-a11y-input-elements rule.
*/
export class Rule extends Lint.Rules.AbstractRule {

public static metadata: ExtendedMetadata = {
ruleName: 'react-a11y-input-elements',
type: 'functionality',
description: 'For accessibility of your website, HTML input boxes and text areas must include default, place-holding characters.',
options: undefined,
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Moderate',
level: 'Opportunity for Excellence',
group: 'Accessibility'
};

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(new ReactA11yInputElementsRuleWalker(sourceFile, this.getOptions()));
} else {
return [];
}
}
}

class ReactA11yInputElementsRuleWalker extends ErrorTolerantWalker {

protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
const tagName = node.tagName.getText();

if (tagName === 'input') {
const attributes = getJsxAttributesFromJsxElement(node);
if (isEmpty(attributes.value) && isEmpty(attributes.placeholder)) {
this.addFailureAt(node.getStart(), node.getWidth(), MISSING_PLACEHOLDER_INPUT_FAILURE_STRING);
}
} else if (tagName === 'textarea') {
const attributes = getJsxAttributesFromJsxElement(node);
if (isEmpty(attributes.placeholder)) {
this.addFailureAt(node.getStart(), node.getWidth(), MISSING_PLACEHOLDER_TEXTAREA_FAILURE_STRING);
}
}
super.visitJsxSelfClosingElement(node);
}

protected visitJsxElement(node: ts.JsxElement): void {
const tagName = node.openingElement.tagName.getText();
const attributes: { [propName: string]: ts.JsxAttribute } = getJsxAttributesFromJsxElement(node);

if (tagName === 'textarea') {
if (node.children.length === 0 && isEmpty(attributes.placeholder)) {
this.addFailureAt(node.getStart(), node.getWidth(), MISSING_PLACEHOLDER_TEXTAREA_FAILURE_STRING);
}
}
super.visitJsxElement(node);
}
}
56 changes: 56 additions & 0 deletions src/tests/ReactA11yInputElementsRuleTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {Utils} from '../utils/Utils';
import {TestHelper} from './TestHelper';
import {
MISSING_PLACEHOLDER_INPUT_FAILURE_STRING,
MISSING_PLACEHOLDER_TEXTAREA_FAILURE_STRING
} from '../reactA11yInputElementsRule';

/**
* Unit tests.
*/
describe('reactA11yInputElementsRule', () : void => {

const ruleName : string = 'react-a11y-input-elements';

it('should pass on input elements with placeholder', () : void => {
const script : string = `
import React = require('react');
const a = <input value="someValue" />;
const b = <input placeholder="default placeholder" />;
const c = <textarea placeholder="default placeholder" />;
const d = <textarea>Some text</textarea>;
`;

TestHelper.assertViolations(ruleName, script, [ ]);
});

it('should fail on empty input elements without placeholder', () : void => {
const script : string = `
import React = require('react');
const a = <input />;
const b = <textarea />;
const c = <textarea></textarea>;
`;

TestHelper.assertViolations(ruleName, script, [
{
"failure": MISSING_PLACEHOLDER_INPUT_FAILURE_STRING,
"name": Utils.absolutePath("file.tsx"),
"ruleName": "react-a11y-input-elements",
"startPosition": { "character": 23, "line": 3 }
},
{
"failure": MISSING_PLACEHOLDER_TEXTAREA_FAILURE_STRING,
"name": Utils.absolutePath("file.tsx"),
"ruleName": "react-a11y-input-elements",
"startPosition": { "character": 23, "line": 4 }
},
{
"failure": MISSING_PLACEHOLDER_TEXTAREA_FAILURE_STRING,
"name": Utils.absolutePath("file.tsx"),
"ruleName": "react-a11y-input-elements",
"startPosition": { "character": 23, "line": 5 }}
]);
});

});
1 change: 1 addition & 0 deletions tslint-warnings.csv
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ react-a11y-aria-unsupported-elements,"Enforce that elements that do not support
react-a11y-event-has-role,Elements with event handlers must have role attribute.,TSLINT18MKF94,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-image-button-has-alt,Enforce that inputs element with type="image" must have alt attribute.,TSLINTVBN64L,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-img-has-alt,"Enforce that an img element contains the non-empty alt attribute. For decorative images, using empty alt attribute and role="presentation".",TSLINT1OM69KS,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-input-elements,"For accessibility of your website, HTML input boxes and text areas must include default, place-holding characters.",TSLINTT7DC6U,tslint,Non-SDL,Warning,Moderate,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-lang,"For accessibility of your website, html elements must have a valid lang attribute.",TSLINTQ046RM,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-no-onchange,"For accessibility of your website, enforce usage of onBlur over onChange on select menus.",TSLINTNO0TDD,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-props,Enforce all `aria-*` attributes are valid. Elements cannot use an invalid `aria-*` attribute.,TSLINT1682S78,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@
"space-within-parens": true,
"switch-final-break": true,
"type-literal-delimiter": false,
"use-default-type-parameter": false
"use-default-type-parameter": false,
"react-a11y-input-elements": true
}
}

0 comments on commit 894dc6b

Please sign in to comment.