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

#278 Implement react-a11y-no-onchange. #543

Merged
merged 5 commits into from
Oct 16, 2018
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 @@ -135,6 +135,7 @@ Rule Name | Description | Since
`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-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
`react-a11y-props` | For accessibility of your website, enforce all `aria-*` attributes are valid. Elements cannot use an invalid `aria-*` attribute. This rule will fail if it finds an `aria-*` attribute that is not listed in [WAI-ARIA states and properties](https://www.w3.org/WAI/PF/aria/states_and_properties#state_prop_values). | 2.0.11
`react-a11y-proptypes` | For accessibility of your website, enforce the type of aria state and property values are correct. | 2.0.11
`react-a11y-role-has-required-aria-props` | For accessibility of your website, elements with aria roles must have all required attributes according to the role. <br/>References:<br/>[ARIA Definition of Roles](https://www.w3.org/TR/wai-aria/roles#role_definitions)<br/>[WCAG Rule 90: Required properties and states should be defined](http://oaa-accessibility.org/wcag20/rule/90/)<br/>[WCAG Rule 91: Required properties and states must not be empty](http://oaa-accessibility.org/wcag20/rule/91/)<br/>| 2.0.11
Expand Down
1 change: 1 addition & 0 deletions recommended_ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ module.exports = {
"react-a11y-img-has-alt": true,
"react-a11y-lang": true,
"react-a11y-meta": true,
"react-a11y-no-onchange": true,
"react-a11y-props": true,
"react-a11y-proptypes": true,
"react-a11y-role": true,
Expand Down
64 changes: 64 additions & 0 deletions src/reactA11yNoOnchangeRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as ts from 'typescript';
import * as Lint from 'tslint';

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

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

public static metadata: ExtendedMetadata = {
ruleName: 'react-a11y-no-onchange',
type: 'functionality',
description: 'For accessibility of your website, enforce usage of onBlur over onChange on select menus.',
options: 'string[]',
optionsDescription: 'Additional tag names to validate.',
optionExamples: ['true', '[true, ["Select"]]'],
typescriptOnly: false,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Important',
level: 'Opportunity for Excellence',
group: 'Accessibility'
};

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

class ReactA11yNoOnchangeRuleWalker extends ErrorTolerantWalker {
protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
this.checkJsxOpeningElement(node);
super.visitJsxSelfClosingElement(node);
}

protected visitJsxElement(node: ts.JsxElement): void {
this.checkJsxOpeningElement(node.openingElement);
super.visitJsxElement(node);
}

private checkJsxOpeningElement(node: ts.JsxOpeningLikeElement) {
const tagName: string = node.tagName.getText();
const options: any[] = this.getOptions();

const additionalTagNames: string[] = options.length > 0 ? options[0] : [];

const targetTagNames: string[] = ['select', ...additionalTagNames];

if (!tagName || targetTagNames.indexOf(tagName) === -1) {
return;
}

const attributes = getJsxAttributesFromJsxElement(node);
if (attributes.hasOwnProperty('onchange')) {
const errorMessage = `onChange event handler should not be used with the <${tagName}>. Please use onBlur instead.`;
this.addFailureAt(node.getStart(), node.getWidth(), errorMessage);
}
}
}
61 changes: 61 additions & 0 deletions src/tests/ReactA11yNoOnchangeRuleTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {Utils} from '../utils/Utils';
import {TestHelper} from './TestHelper';

/**
* Unit tests.
*/
describe('reactA11yNoOnchangeRule', () : void => {
const ruleName : string = 'react-a11y-no-onchange';
const errorMessage = (tagName: string): string =>
`onChange event handler should not be used with the <${tagName}>. Please use onBlur instead.`;

it('should pass if select element attributes without onChange event', () : void => {
const script: string = `
import React = require('react');
const selectElement = <select />
const selectElementWithOnBlur = <select onBlur={} />`;

TestHelper.assertNoViolation(ruleName, script);
});

it('should fail if select element attributes contains onChange event', () : void => {
const script : string = `
import React = require('react');
const selectElementWithOnChange = <select onChange={} />\`;
`;

TestHelper.assertViolations(ruleName, script, [{
failure: errorMessage('select'),
name: Utils.absolutePath('file.tsx'),
ruleName,
ruleSeverity: "ERROR",
startPosition: {character: 47, line: 3}
}]);
});

it('should fail if additional tag name specified in options contains onChange event', () => {
const script : string = `
import React = require('react');
const selectElementWithOnChange = <Select onChange={} />
const selectElementWithOnChange = <select onChange={} />
`;

TestHelper.assertViolationsWithOptions(ruleName, ['Select'], script, [{
failure: errorMessage('Select'),
name: Utils.absolutePath('file.tsx'),
ruleName,
ruleSeverity: "ERROR",
startPosition: {character: 47, line: 3}
}, {
failure: errorMessage('select'),
name: Utils.absolutePath('file.tsx'),
ruleName,
ruleSeverity: 'ERROR',
startPosition: {
'character': 47,
'line': 4
}
}]);
});

});
1 change: 1 addition & 0 deletions tslint-warnings.csv
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ react-a11y-event-has-role,Elements with event handlers must have role attribute.
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-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,,
react-a11y-proptypes,Enforce ARIA state and property values are valid.,TSLINT1DLB1JE,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-role,"Elements with aria roles must use a **valid**, **non-abstract** aria role.",TSLINTQ0A2FU,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
Expand Down
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
"react-a11y-img-has-alt": true,
"react-a11y-lang": true,
"react-a11y-meta": true,
"react-a11y-no-onchange": true,
"react-a11y-props": true,
"react-a11y-proptypes": true,
"react-a11y-role": true,
Expand Down