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

Fix #275 Add new rule for accessible input elements #547

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved

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
}
}