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

Commit

Permalink
[Issue #196] new rule: react-a11y-meta
Browse files Browse the repository at this point in the history
  • Loading branch information
HamletDRC committed Aug 29, 2016
1 parent 548c6f3 commit 23645be
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Rule Name | Description | Since
`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-lang` | For accessibility of your website, html elements must have a lang attribute.<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) | 2.0.11
`react-a11y-meta` | ... todo | 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-titles` | For accessibility of your website, HTML title elements must not be empty.<br/>References:<br/>* [WCAG 2.0 - Requirement 2.4.2 Page Titled (Level A)](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-title)<br/>* [OAA-Accessibility Rule 13: Title element should not be empty](http://oaa-accessibility.org/wcag20/rule/13/) | 2.0.11
`react-no-dangerous-html` | Do not use React's dangerouslySetInnerHTML API. This rule finds usages of the dangerouslySetInnerHTML API (but not any JSX references). For more info see the [react-no-dangerous-html Rule wiki page](https://github.com/Microsoft/tslint-microsoft-contrib/wiki/react-no-dangerous-html-Rule). | 0.0.2
`react-this-binding-issue` | Several errors can occur when using React and React.Component subclasses. When using React components you must be careful to correctly bind the 'this' reference on any methods that you pass off to child components as callbacks. For example, it is common to define a private method called 'onClick' and then specify `onClick={this.onClick}` as a JSX attribute. If you do this then the 'this' reference will be undefined when your private method is invoked. The React documentation suggests that you bind the 'this' reference on all of your methods within the constructor: `this.onClick = this.onClick.bind(this);`. This rule will create a violation if 1) a method reference is passed to a JSX attribute without being bound in the constructor. And 2) a method is bound multiple times in the constructor. Another issue that can occur is binding the 'this' reference to a function within the render() method. For example, many people will create an anonymous lambda within the JSX attribute to avoid the 'this' binding issue: `onClick={() => { this.onClick(); }}`. The problem with this is that a new instance of an anonymous function is created every time render() is invoked. When React compares virutal DOM properties within shouldComponentUpdate() then the onClick property will look like a new property and force a re-render. You should avoid this pattern because creating function instances within render methods breaks any logic within shouldComponentUpdate() methods. This rule creates violations if 1) an anonymous function is passed as a JSX attribute. And 2) if a function instantiated in local scope is passed as a JSX attribute. This rule can be configured via the "allow-anonymous-listeners" parameter. If you want to suppress violations for the anonymous listener scenarios then configure that rule like this: `"react-this-binding-issue": [ true, { 'allow-anonymous-listeners': true } ]` | 2.0.8, 2.0.9
Expand Down
1 change: 1 addition & 0 deletions recommended_ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ module.exports = {
* experience for keyboard and screen reader users.
*/
"react-a11y-lang": true,
"react-a11y-meta": true,
"react-a11y-titles": true,

/**
Expand Down
74 changes: 74 additions & 0 deletions src/reactA11yMetaRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as ts from 'typescript';
import * as Lint from 'tslint/lib/lint';

import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker';
import {ExtendedMetadata} from './utils/ExtendedMetadata';
import {SyntaxKind} from './utils/SyntaxKind';

const FAILURE_STRING: string = 'Do not use http-equiv="refresh"';

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

public static metadata: ExtendedMetadata = {
ruleName: 'react-a11y-meta',
type: 'functionality',
description: '... add a meaningful one line description',
options: null,
issueClass: 'Ignored',
issueType: 'Warning',
severity: 'Low',
level: 'Opportunity for Excellence',
group: 'Accessibility'
};

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new ReactA11yMetaRuleWalker(sourceFile, this.getOptions()));
}
}

class ReactA11yMetaRuleWalker extends ErrorTolerantWalker {


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

protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
this.validateOpeningElement(node, node);
}

private validateOpeningElement(parent: ts.Node, openElement: ts.JsxOpeningElement): void {
if (openElement.tagName.getText() === 'meta') {
const attributes: ts.NodeArray<ts.JsxAttribute | ts.JsxSpreadAttribute> = openElement.attributes;
attributes.forEach((parameter: ts.JsxAttribute | ts.JsxSpreadAttribute): void => {
if (parameter.kind === SyntaxKind.current().JsxAttribute) {
const attribute: ts.JsxAttribute = <ts.JsxAttribute>parameter;
if (attribute.name.getText() === 'http-equiv') {
if (attribute.initializer != null) {
if (attribute.initializer.kind === SyntaxKind.current().StringLiteral) {
const value: string = (<ts.StringLiteral>attribute.initializer).text;
if (value === 'refresh') {
this.addFailure(this.createFailure(parent.getStart(), openElement.getWidth(), FAILURE_STRING));
}
} else if (attribute.initializer.kind === SyntaxKind.current().JsxExpression) {
const exp: ts.JsxExpression = <ts.JsxExpression>attribute.initializer;
if (exp.expression.kind === SyntaxKind.current().StringLiteral) {
const value: string = (<ts.StringLiteral>exp.expression).text;
if (value === 'refresh') {
this.addFailure(
this.createFailure(openElement.getStart(), openElement.getWidth(), FAILURE_STRING)
);
}
}
}
}
}
}
});
}
}
}
74 changes: 74 additions & 0 deletions tests/ReactA11yMetaRuleTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/// <reference path="../typings/mocha.d.ts" />
/// <reference path="../typings/chai.d.ts" />

import {TestHelper} from './TestHelper';

/**
* Unit tests.
*/
describe('reactA11yMetaRule', () : void => {

const ruleName : string = 'react-a11y-meta';

it('should pass on meta tags without refresh', () : void => {
const script : string = `
import React = require('react');
const x = <meta http-equiv="not_refresh" />
`;

TestHelper.assertViolations(ruleName, script, [ ]);
});

it('should fail on meta tags with refresh - self closing', () : void => {
const script : string = `
import React = require('react');
const x = <meta http-equiv="refresh" />
`;

TestHelper.assertViolations(ruleName, script, [
{
"failure": "Do not use http-equiv=\"refresh\"",
"name": "file.tsx",
"ruleName": "react-a11y-meta",
"startPosition": { "character": 23, "line": 4 }
}
]);
});

it('should fail on meta tags with refresh', () : void => {
const script : string = `
import React = require('react');
const x = <meta http-equiv="refresh" ></meta>
`;

TestHelper.assertViolations(ruleName, script, [
{
"failure": "Do not use http-equiv=\"refresh\"",
"name": "file.tsx",
"ruleName": "react-a11y-meta",
"startPosition": { "character": 23, "line": 4 }
}
]);
});

it('should fail on meta tags with refresh - self-closing', () : void => {
const script : string = `
import React = require('react');
const x = <meta http-equiv={"refresh"} />
`;

TestHelper.assertViolations(ruleName, script, [
{
"failure": "Do not use http-equiv=\"refresh\"",
"name": "file.tsx",
"ruleName": "react-a11y-meta",
"startPosition": { "character": 23, "line": 4 }
}
]);
});

});
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
"check-separator",
"check-type"
],
"react-a11y-lang": true
"react-a11y-lang": true,
"react-a11y-meta": true
}
}

0 comments on commit 23645be

Please sign in to comment.