Implement react-a11y-iframes rule (#692)

* implement reactA11yIFramesRule
add unit tests

* generate README and tslint.json

* generate README and tslint.json

* Rename reactA11yIFramesRule.ts to reactA11yIframesRule.ts

* Rename reactA11yIFramesRuleTests.ts to reactA11yIframesRuleTests.ts

* add call to super, fix README.md, lowercase tsUtils

* add tslint tests
fix build error (String -> string)

* update tslint-warnings after npm install

* remove mocha test
add comma to rule description
fix function names in test.tsx.lint

* refactor reactA11yIframsRule to use walk function
add unit tests for class and function rule enforcement

* remove resetting previousTiltles when JsxFragment is enountered

* add some test cases
This commit is contained in:
Noam Yogev 2019-01-19 22:52:14 +02:00 коммит произвёл Josh Goldberg
Родитель bd9b40d38b
Коммит f67d898d74
6 изменённых файлов: 165 добавлений и 0 удалений

Просмотреть файл

@ -151,6 +151,15 @@ We recommend you specify exact versions of lint libraries, including `tslint-mic
</td> </td>
<td>6.0.0</td> <td>6.0.0</td>
</tr> </tr>
<tr>
<td>
<code>react-a11y-iframes</code>
</td>
<td>
Enforce that iframe elements are not empty, have title, and are unique.
</td>
<td>6.1.0</td>
</tr>
<tr> <tr>
<td> <td>
<code>informative-docs</code> <code>informative-docs</code>

Просмотреть файл

@ -0,0 +1,82 @@
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as tsutils from 'tsutils';
import { ExtendedMetadata } from './utils/ExtendedMetadata';
import { getJsxAttributesFromJsxElement } from './utils/JsxAttribute';
const IFRAME_ELEMENT_NAME: string = 'iframe';
const TITLE_ATTRIBUTE_NAME: string = 'title';
const SRC_ATTRIBUTE_NAME: string = 'src';
const HIDDEN_ATTRIBUTE_NAME: string = 'hidden';
const IFRAME_EMPTY_TITLE_ERROR_STRING: string = 'An iframe element must have a non-empty title.';
const IFRAME_EMPTY_OR_HIDDEN_ERROR_STRING: string = 'An iframe element should not be hidden or empty.';
const IFRAME_UNIQUE_TITLE_ERROR_STRING: string = 'An iframe element must have a unique title.';
export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'react-a11y-iframes',
type: 'functionality',
description: 'Enforce that iframe elements are not empty, have title, and are unique.',
options: null, // tslint:disable-line:no-null-keyword
optionsDescription: '',
typescriptOnly: false,
issueClass: 'Non-SDL',
issueType: 'Error',
severity: 'Important',
level: 'Opportunity for Excellence',
group: 'Accessibility'
};
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return sourceFile.languageVariant === ts.LanguageVariant.JSX ? this.applyWithFunction(sourceFile, walk) : [];
}
}
function walk(ctx: Lint.WalkContext<void>) {
const previousTitles: Set<string> = new Set();
function cb(node: ts.Node): void {
if (tsutils.isVariableDeclaration(node) || tsutils.isMethodDeclaration(node) || tsutils.isFunctionDeclaration(node)) {
previousTitles.clear();
}
if (tsutils.isJsxOpeningElement(node) || tsutils.isJsxSelfClosingElement(node)) {
if (node.tagName.getText() === IFRAME_ELEMENT_NAME) {
const attributes = getJsxAttributesFromJsxElement(node);
// Validate that iframe has a non-empty title
const titleAttribute = attributes[TITLE_ATTRIBUTE_NAME];
const titleAttributeText = getAttributeText(titleAttribute);
if (!titleAttribute || !titleAttributeText) {
ctx.addFailureAtNode(node.tagName, IFRAME_EMPTY_TITLE_ERROR_STRING);
}
// Validate the iframe title is unique
if (titleAttributeText && previousTitles.has(titleAttributeText)) {
ctx.addFailureAtNode(node.tagName, IFRAME_UNIQUE_TITLE_ERROR_STRING);
} else if (titleAttributeText) {
previousTitles.add(titleAttributeText);
}
// Validate that iframe is not empty or hidden
const hiddenAttribute = attributes[HIDDEN_ATTRIBUTE_NAME];
const srcAttribute = attributes[SRC_ATTRIBUTE_NAME];
if (hiddenAttribute || !srcAttribute || !getAttributeText(srcAttribute)) {
ctx.addFailureAtNode(node.tagName, IFRAME_EMPTY_OR_HIDDEN_ERROR_STRING);
}
}
}
return ts.forEachChild(node, cb);
}
return ts.forEachChild(ctx.sourceFile, cb);
}
function getAttributeText(attribute: ts.JsxAttribute): string | undefined {
if (attribute && attribute.initializer) {
if (tsutils.isJsxExpression(attribute.initializer)) {
return attribute.initializer.expression ? attribute.initializer.expression.getText() : undefined;
}
if (tsutils.isStringLiteral(attribute.initializer)) {
return attribute.initializer.text;
}
}
return undefined;
}

Просмотреть файл

@ -0,0 +1,67 @@
const SomeComponent = () => <iframe title="I'm a non empty title" src="http://someSource.com"/>;
const SomeComponent = () => <iframe title={"hello there"} src="http://someSource.com"/>;
const SomeComponent = () => <iframe title={someVar} src="http://someSource.com"></iframe>
const SomeComponent = () => <iframe title={someVar} src="{anotherVar}"></iframe>
const SomeComponent = () => <iframe title="" src="http://someSource.com"/>;
~~~~~~ [An iframe element must have a non-empty title.]
const SomeComponent = () => <iframe src="http://someSource.com" />;
~~~~~~ [An iframe element must have a non-empty title.]
const SomeComponent = () => <iframe title="hi there"></iframe>;
~~~~~~ [An iframe element should not be hidden or empty.]
const SomeComponent = () => <iframe title="hi there" src=""/>;
~~~~~~ [An iframe element should not be hidden or empty.]
const SomeComponent = () => <iframe title="hi there" src="http://someSource.com" hidden></iframe>;
~~~~~~ [An iframe element should not be hidden or empty.]
const SomeComponent = () =>
<>
<iframe title="hi there" src="http://someSource.com"></iframe>
<iframe title="hello there" src="http://someSource.com"></iframe>
</>
const SomeComponent = () =>
<>
<iframe title="hi there" src="http://someSource.com"></iframe>
<iframe title="hi there" src="http://someSource.com"></iframe>
~~~~~~ [An iframe element must have a unique title.]
</>
class AmazingComponent extends React.Component {
render() {
return (
<>
<iframe src="http://someSource.com" />
~~~~~~ [An iframe element must have a non-empty title.]
<iframe title="Our sponsors" src="" />
~~~~~~ [An iframe element should not be hidden or empty.]
<iframe title="i'm so great" src="http://someSource.com" hidden></iframe>
~~~~~~ [An iframe element should not be hidden or empty.]
<iframe title="i'm so great" src="http://someSource.com" />
~~~~~~ [An iframe element must have a unique title.]
</>
)
}
}
function AmazingComponent() {
const someElement = () =>
<>
<iframe title="some title" src="http://someSource.com" hidden></iframe>
~~~~~~ [An iframe element should not be hidden or empty.]
<iframe title="some title" src="http://someSource.com" />
~~~~~~ [An iframe element must have a unique title.]
</>
return (
<>
<iframe src="http://someSource.com" />
~~~~~~ [An iframe element must have a non-empty title.]
<iframe title="Our sponsors" src="" />
~~~~~~ [An iframe element should not be hidden or empty.]
<iframe title="i'm so great" src="http://someSource.com" hidden></iframe>
~~~~~~ [An iframe element should not be hidden or empty.]
<iframe title="i'm so great" src="http://someSource.com" />
~~~~~~ [An iframe element must have a unique title.]
</>
)
}

Просмотреть файл

@ -0,0 +1,5 @@
{
"rules": {
"react-a11y-iframes": true
}
}

Просмотреть файл

@ -261,6 +261,7 @@ react-a11y-accessible-headings,"For accessibility of your website, there should
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-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-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-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-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-iframes,"Enforce that iframe elements are not empty, have title, and are unique.",TSLINT1ABJ11P,tslint,Non-SDL,Error,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-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-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-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,,

Не удается отобразить этот файл, потому что он содержит неожиданный символ в строке 156 и столбце 45.

Просмотреть файл

@ -126,6 +126,7 @@
"react-a11y-anchors": true, "react-a11y-anchors": true,
"react-a11y-aria-unsupported-elements": true, "react-a11y-aria-unsupported-elements": true,
"react-a11y-event-has-role": true, "react-a11y-event-has-role": true,
"react-a11y-iframes": true,
"react-a11y-image-button-has-alt": true, "react-a11y-image-button-has-alt": true,
"react-a11y-img-has-alt": true, "react-a11y-img-has-alt": true,
"react-a11y-input-elements": true, "react-a11y-input-elements": true,