diff --git a/README.md b/README.md index 89ca0a3ef..5868988a5 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ Rule Name | Description | Since `prefer-type-cast` | Prefer the tradition type casts instead of the new 'as-cast' syntax. For example, prefer '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-lang` | For accessibility of your website, html elements must have a lang attribute.
References:
* [H58: Using language attributes to identify changes in the human language](https://www.w3.org/TR/WCAG20-TECHS/H58.html)
* [lang attribute must have a valid value](https://dequeuniversity.com/rules/axe/1.1/valid-lang) | 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.
References:
* [H58: Using language attributes to identify changes in the human language](https://www.w3.org/TR/WCAG20-TECHS/H58.html)
* [lang attribute must have a valid value](https://dequeuniversity.com/rules/axe/1.1/valid-lang)
[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-titles` | For accessibility of your website, HTML title elements must not be empty, must be more than one word, and must not be more than 60 characters long.
References:
* [WCAG 2.0 - Requirement 2.4.2 Page Titled (Level A)](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-title)
* [OAA-Accessibility Rule 13: Title element should not be empty](http://oaa-accessibility.org/wcag20/rule/13/)
* [OAA-Accessibility Rule 24: Title content should be concise](http://oaa-accessibility.org/wcag20/rule/24/)
* [OAA-Accessibility Rule 25: Title text must contain more than one word](http://oaa-accessibility.org/wcag20/rule/25/)
| 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 diff --git a/src/reactA11yLangRule.ts b/src/reactA11yLangRule.ts index d777ed2f1..afd7bcb83 100644 --- a/src/reactA11yLangRule.ts +++ b/src/reactA11yLangRule.ts @@ -5,7 +5,22 @@ import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; import {SyntaxKind} from './utils/SyntaxKind'; -const FAILURE_STRING: string = 'An html element is missing the lang attribute'; +const FAILURE_MISSING_LANG: string = 'An html element is missing the lang attribute'; +const FAILURE_WRONG_LANG_CODE: string = 'Lang attribute does not have a valid value. Found: '; + +const LANGUAGE_CODES: string[] = [ + 'ab', 'aa', 'af', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'ay', 'az', 'ba', 'eu', 'bn', + 'dz', 'bh', 'bi', 'br', 'bg', 'my', 'be', 'km', 'ca', 'zh', 'zh-Hans', 'zh-Hant', + 'co', 'hr', 'cs', 'da', 'nl', 'en', 'eo', 'et', 'fo', 'fa', 'fj', 'fi', 'fr', 'fy', + 'gl', 'gd', 'gv', 'ka', 'de', 'el', 'kl', 'gn', 'gu', 'ht', 'ha', 'he', 'iw', 'hi', + 'hu', 'is', 'io', 'id', 'in', 'ia', 'ie', 'iu', 'ik', 'ga', 'it', 'ja', 'jv', 'kn', + 'ks', 'kk', 'rw', 'ky', 'rn', 'ko', 'ku', 'lo', 'la', 'lv', 'li', 'ln', 'lt', 'mk', + 'mg', 'ms', 'ml', 'mt', 'mi', 'mr', 'mo', 'mn', 'na', 'ne', 'no', 'oc', 'or', 'om', + 'ps', 'pl', 'pt', 'pa', 'qu', 'rm', 'ro', 'ru', 'sm', 'sg', 'sa', 'sr', 'sh', 'st', + 'tn', 'sn', 'ii', 'sd', 'si', 'ss', 'sk', 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tl', + 'tg', 'ta', 'tt', 'te', 'th', 'bo', 'ti', 'to', 'ts', 'tr', 'tk', 'tw', 'ug', 'uk', + 'ur', 'uz', 'vi', 'vo', 'wa', 'cy', 'wo', 'xh', 'yi', 'ji', 'yo', 'zu' +]; /** * Implementation of the react-a11y-lang rule. @@ -15,9 +30,9 @@ export class Rule extends Lint.Rules.AbstractRule { public static metadata: ExtendedMetadata = { ruleName: 'react-a11y-lang', type: 'functionality', - description: 'For accessibility of your website, html elements must have a lang attribute.', + description: 'For accessibility of your website, html elements must have a valid lang attribute.', options: null, - issueClass: 'Ignored', + issueClass: 'Non-SDL', issueType: 'Warning', severity: 'Low', level: 'Opportunity for Excellence', @@ -48,16 +63,27 @@ class ReactA11yLangRuleWalker extends ErrorTolerantWalker { private validateOpeningElement(parent: ts.Node, openingElement: ts.JsxOpeningElement): void { if (openingElement.tagName.getText() === 'html') { const attributes: ts.NodeArray = openingElement.attributes; - let found: boolean = false; + let langFound: boolean = false; + attributes.forEach((attribute: ts.JsxAttribute | ts.JsxSpreadAttribute): void => { if (attribute.kind === SyntaxKind.current().JsxAttribute) { if ((attribute).name.getText() === 'lang') { - found = true; + langFound = true; + if ((attribute).initializer.kind === SyntaxKind.current().StringLiteral) { + const langText: string = ((attribute).initializer).text; + if ((LANGUAGE_CODES.indexOf(langText)) === -1) { + this.addFailure(this.createFailure( + parent.getStart(), + parent.getWidth(), + FAILURE_WRONG_LANG_CODE + langText + )); + } + } } } }); - if (!found) { - this.addFailure(this.createFailure(parent.getStart(), parent.getWidth(), FAILURE_STRING)); + if (!langFound) { + this.addFailure(this.createFailure(parent.getStart(), parent.getWidth(), FAILURE_MISSING_LANG)); } } } diff --git a/tests/ReactA11yLangRuleTests.ts b/tests/ReactA11yLangRuleTests.ts index 34c019653..17236909a 100644 --- a/tests/ReactA11yLangRuleTests.ts +++ b/tests/ReactA11yLangRuleTests.ts @@ -14,8 +14,8 @@ describe('reactA11yLangRule', () : void => { const script : string = ` import React = require('react'); - const x = ; - const y = ; + const x = ; + const y = ; `; TestHelper.assertViolations(ruleName, script, [ ]); @@ -54,4 +54,29 @@ describe('reactA11yLangRule', () : void => { } ]); }); + + it('should fail on invalid language code', () : void => { + const script : string = ` + import React = require('react'); + + const x = ; + const y = ; + `; + + TestHelper.assertViolations(ruleName, script, [ + { + "failure": "Lang attribute does not have a valid value. Found: foo", + "name": "file.tsx", + "ruleName": "react-a11y-lang", + "startPosition": { "character": 23, "line": 4 } + }, + { + "failure": "Lang attribute does not have a valid value. Found: bar", + "name": "file.tsx", + "ruleName": "react-a11y-lang", + "startPosition": { "character": 23, "line": 5 } + } + ]); + }); + }); diff --git a/tslint-warnings.csv b/tslint-warnings.csv index 5617bef3f..d43551724 100644 --- a/tslint-warnings.csv +++ b/tslint-warnings.csv @@ -198,6 +198,7 @@ promise-must-complete,"When a Promise instance is created, then either the rejec quotemark,Requires single or double quotes for string literals.,TSLINTU8MMGA,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,"398, 710","CWE 398 - Indicator of Poor Code Quality 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-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-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-iframe-missing-sandbox,React iframes must specify a sandbox attribute,TSLINTU1H6V4,tslint,Non-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')