diff --git a/src/reactA11yLangRule.ts b/src/reactA11yLangRule.ts index e890e9563..666a107c1 100644 --- a/src/reactA11yLangRule.ts +++ b/src/reactA11yLangRule.ts @@ -5,7 +5,8 @@ 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.'; /** * Implementation of the react-a11y-lang rule. @@ -15,7 +16,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static metadata: ExtendedMetadata = { ruleName: 'react-a11y-lang', type: 'functionality', - description: '... add a meaningful one line description', + description: 'html element should have a valid lang attribute', options: null, issueClass: 'Ignored', issueType: 'Warning', @@ -30,6 +31,19 @@ export class Rule extends Lint.Rules.AbstractRule { } class ReactA11yLangRuleWalker extends ErrorTolerantWalker { + private static languagesISO = [ + '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' + ]; protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void { this.validateOpeningElement(node, node); @@ -44,16 +58,26 @@ 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; + let validLangCode: 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; + let langText: string = (attribute).initializer.getText().trim(); + langText = langText.slice(1, -1); // 'en' -> en + if ((ReactA11yLangRuleWalker.languagesISO.indexOf(langText)) > -1) { + validLangCode = true; + } } } }); - 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)); + } else if (!validLangCode) { + this.addFailure(this.createFailure(parent.getStart(), parent.getWidth(), FAILURE_WRONG_LANG_CODE)); } } } diff --git a/tests/ReactA11yLangRuleTests.ts b/tests/ReactA11yLangRuleTests.ts index 34c019653..1849450ec 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.", + "name": "file.tsx", + "ruleName": "react-a11y-lang", + "startPosition": { "character": 23, "line": 4 } + }, + { + "failure": "Lang attribute does not have a valid value.", + "name": "file.tsx", + "ruleName": "react-a11y-lang", + "startPosition": { "character": 23, "line": 5 } + } + ]); + }); + });