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

Commit

Permalink
[Issue #247] new rule: react-a11y-aria-unsupported-elements
Browse files Browse the repository at this point in the history
closes #243
closes #247
  • Loading branch information
t-ligu authored and HamletDRC committed Sep 15, 2016
1 parent 209b4b2 commit bc5aef0
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ Rule Name | Description | Since
`prefer-type-cast` | Prefer the tradition type casts instead of the new 'as-cast' syntax. For example, prefer '<string>myVariable' instead of 'myVariable as string'. Rule ignores any file ending in .tsx. If you prefer the opposite and want to see the 'as type' casts, then enable the tslint rule named 'no-angle-bracket-type-assertion'| 2.0.4
`promise-must-complete` | When a Promise instance is created, then either the reject() or resolve() parameter must be called on it within all code branches in the scope. For more examples see the [feature request](https://github.com/Microsoft/tslint-microsoft-contrib/issues/34). | 1.0
`react-iframe-missing-sandbox` | React iframes must specify a sandbox attribute. If specified as an empty string, this attribute enables extra restrictions on the content that can appear in the inline frame. The value of the attribute can either be an empty string (all the restrictions are applied), or a space-separated list of tokens that lift particular restrictions. You many not use both allow-scripts and allow-same-origin at the same time, as that allows the embedded document to programmatically remove the sandbox attribute in some scenarios. | 2.0.10
`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-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-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
Expand Down
4 changes: 4 additions & 0 deletions recommended_ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
"no-string-based-set-immediate": true,
"no-string-based-set-interval": true,
"no-string-based-set-timeout": true,
"react-anchor-blank-noopener": true,
"react-iframe-missing-sandbox": true,
"react-no-dangerous-html": true,
"use-strict": true,
Expand Down Expand Up @@ -137,10 +138,13 @@ module.exports = {
* experience for keyboard and screen reader users.
*/
"react-a11y-anchors": true,
"react-a11y-aria-unsupported-elements": true,
"react-a11y-image-button-has-alt": true,
"react-a11y-img-has-alt": true,
"react-a11y-lang": true,
"react-a11y-meta": true,
"react-a11y-props": true,
"react-a11y-proptypes": true,
"react-a11y-role": true,
"react-a11y-role-has-required-aria-props": true,
"react-a11y-role-supports-aria-props": true,
Expand Down
78 changes: 78 additions & 0 deletions src/reactA11yAriaUnsupportedElementsRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Enforce that elements that do not support ARIA roles, states, and properties do not have those attributes.
*/
import * as ts from 'typescript';
import * as Lint from 'tslint/lib/lint';

import { ExtendedMetadata } from './utils/ExtendedMetadata';
import { getJsxAttributesFromJsxElement} from './utils/JsxAttribute';
import { IDom } from './utils/attributes/IDom';
import { IAria } from './utils/attributes/IAria';

// tslint:disable:no-require-imports no-var-requires
const DOM_SCHEMA: IDom[] = require('./utils/attributes/domSchema.json');
const ARIA_SCHEMA: IAria[] = require('./utils/attributes/ariaSchema.json');
// tslint:enable:no-require-imports no-var-requires

export function getFailureString(tagName: string, ariaAttributeNames: string[]): string {
return `This element ${tagName} does not support ARIA roles, states and properties. `
+ `Try removing attribute(s): ${ariaAttributeNames.join(', ')}.`;
}

export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'react-a11y-aria-unsupported-elements',
type: 'maintainability',
description: 'Enforce that elements that do not support ARIA roles, states, and properties do not have those attributes.',
options: null,
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 ReactA11yAriaUnsupportedElementsWalker(sourceFile, this.getOptions()))
: [];
}
}

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

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

private validateOpeningElement(node: ts.JsxOpeningElement): void {
const tagName: string = node.tagName.getText();

if (!DOM_SCHEMA[tagName]) {
return;
}

const unsupportedAria: boolean = DOM_SCHEMA[tagName].unsupportedAria != null
? DOM_SCHEMA[tagName].unsupportedAria
: false;

if (!unsupportedAria) {
return;
}

const checkAttributeNames: string[] = Object.keys(ARIA_SCHEMA).concat('role');
const attributes: { [propName: string]: ts.JsxAttribute } = getJsxAttributesFromJsxElement(node);
const invalidAttributeNames: string[] =
checkAttributeNames.filter((attributeName: string): boolean => !!attributes[attributeName]);

if (invalidAttributeNames.length > 0) {
const message: string = getFailureString(tagName, invalidAttributeNames);
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), message));
}
}
}
6 changes: 6 additions & 0 deletions src/utils/attributes/IDom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Interface of dom
*/
export interface IDom {
unsupportedAria: boolean;
}
145 changes: 145 additions & 0 deletions src/utils/attributes/domSchema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"a": {},
"abbr": {},
"address": {},
"area": {},
"article": {},
"aside": {},
"audio": {},
"b": {},
"base": {
"unsupportedAria": true
},
"bdi": {},
"bdo": {},
"big": {},
"blockquote": {},
"body": {},
"br": {},
"button": {},
"canvas": {},
"caption": {},
"cite": {},
"code": {},
"col": {
"unsupportedAria": true
},
"colgroup": {
"unsupportedAria": true
},
"data": {},
"datalist": {},
"dd": {},
"del": {},
"details": {},
"dfn": {},
"dialog": {},
"div": {},
"dl": {},
"dt": {},
"em": {},
"embed": {},
"fieldset": {},
"figcaption": {},
"figure": {},
"footer": {},
"form": {},
"h1": {},
"h2": {},
"h3": {},
"h4": {},
"h5": {},
"h6": {},
"head": {
"unsupportedAria": true
},
"header": {},
"hgroup": {},
"hr": {},
"html": {
"unsupportedAria": true
},
"i": {},
"iframe": {},
"img": {},
"input": {},
"ins": {},
"kbd": {},
"keygen": {},
"label": {},
"legend": {},
"li": {},
"link": {
"unsupportedAria": true
},
"main": {},
"map": {},
"mark": {},
"menu": {},
"menuitem": {},
"meta": {
"unsupportedAria": true
},
"meter": {},
"nav": {},
"noscript": {
"unsupportedAria": true
},
"object": {},
"ol": {},
"optgroup": {},
"option": {},
"output": {},
"p": {},
"param": {
"unsupportedAria": true
},
"picture": {
"unsupportedAria": true
},
"pre": {},
"progress": {},
"q": {},
"rp": {},
"rt": {},
"ruby": {},
"s": {},
"samp": {},
"script": {
"unsupportedAria": true
},
"section": {},
"select": {},
"small": {},
"source": {
"unsupportedAria": true
},
"span": {},
"strong": {},
"style": {
"unsupportedAria": true
},
"sub": {},
"summary": {},
"sup": {},
"table": {},
"tbody": {},
"td": {},
"textarea": {},
"tfoot": {},
"th": {},
"thead": {},
"time": {},
"title": {
"unsupportedAria": true
},
"tr": {},
"track": {
"unsupportedAria": true
},
"u": {},
"ul": {},
"var": {},
"video": {},
"wbr": {}
}
56 changes: 56 additions & 0 deletions tests/ReactA11yAriaUnsupportedElementsRuleTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { TestHelper } from './TestHelper';
import { getFailureString } from '../src/reactA11yAriaUnsupportedElementsRule';

/**
* Unit test for react-a11y-aria-unsupported-elements rule.
*/
describe('reactA11yAriaUnsupportedElementsRule', () => {
const ruleName: string = 'react-a11y-aria-unsupported-elements';

it('should pass when tag name is not dom elements', (): void => {
const script: string = `
import React = require('react);
const a = <DIV aria-label/>;
const b = <DIV role></DIV>;
`;
TestHelper.assertNoViolation(ruleName, script);
});

it('should pass when tag name is supported aria element', (): void => {
const script: string = `
import React = require('react);
const a = <div />;
const b = <div aria-label role { ...this.props }></div>;
`;
TestHelper.assertNoViolation(ruleName, script);
});

it('should fail when unsupported aria elements have aria-* or role attributes', (): void => {
const script: string = `
import React = require('react');
const a = <base aria-label role { ...this.props }></base>;
const b = <base aria-label role { ...this.props } />;
`;
TestHelper.assertViolations(
ruleName,
script,
[
{
name: 'file.tsx',
ruleName: ruleName,
startPosition: { character: 23, line: 4 },
failure: getFailureString('base', ['aria-label', 'role'])
},
{
name: 'file.tsx',
ruleName: ruleName,
startPosition: { character: 23, line: 5 },
failure: getFailureString('base', ['aria-label', 'role'])
}
]
);
});
});
5 changes: 5 additions & 0 deletions tslint-warnings.csv
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,19 @@ quotemark,Requires single or double quotes for string literals.,TSLINTU8MMGA,tsl
CWE 710 - Coding Standards Violation"
radix,Requires the radix parameter to be specified when calling `parseInt`.,TSLINTTLKJQ5,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,710,"CWE 710 - Coding Standards Violation"
react-a11y-anchors,"For accessibility of your website, anchor elements must have a href different from # and a text longer than 4.",TSLINT8EMFEM,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-aria-unsupported-elements,"Enforce that elements that do not support ARIA roles, states, and properties do not have those attributes.",TSLINTQ04S5L,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 `alt` attribute or `role="presentation"` for decorative image.,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-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,,
react-a11y-role-has-required-aria-props,Elements with aria roles must have all required attributes according to the role.,TSLINT1R1B60O,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-role-supports-aria-props,Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`.,TSLINT1IFADTQ,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-tabindex-no-positive,Enforce tabindex value is **not greater than zero**.,TSLINTEO7FKT,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-titles,"For accessibility of your website, HTML title elements must be concise and non-empty.",TSLINT1506S53,tslint,Non-SDL,Warning,Moderate,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-anchor-blank-noopener,Anchor tags with target="_blank" should also include rel="noopener noreferrer",TSLINT1GKPCB4,tslint,SDL,Error,Critical,Mandatory,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,"242,676","CWE 242 - Use of Inherently Dangerous Function
CWE 676 - Use of Potentially Dangerous Function"
react-iframe-missing-sandbox,React iframes must specify a sandbox attribute,TSLINTU1H6V4,tslint,SDL,Error,Critical,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,915,"CWE 915 - Improperly Controlled Modification of Dynamically-Determined Object Attributes"
react-no-dangerous-html,Do not use React's dangerouslySetInnerHTML API.,TSLINTPH7BOD,tslint,SDL,Error,Critical,Mandatory,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,"79, 85, 710","CWE 79 - Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
CWE 85 - Doubled Character XSS Manipulations
Expand Down
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"rules": {
"react-a11y-aria-unsupported-elements": true,
"react-a11y-image-button-has-alt": true,
"react-a11y-img-has-alt": true,
"react-a11y-props": true,
Expand Down

0 comments on commit bc5aef0

Please sign in to comment.