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:
Родитель
bd9b40d38b
Коммит
f67d898d74
|
@ -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,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче