From 28326156b774d7f6c317637ce5b82de0b69c86b0 Mon Sep 17 00:00:00 2001 From: Zama Khan Mohammed Date: Wed, 13 Feb 2019 17:03:22 -0600 Subject: [PATCH] feat(rule): only th element can have scope (#743) --- src/index.ts | 1 + src/templateAccessibilityTableScopeRule.ts | 55 +++++++++++++++++ ...emplateAccessibilityTableScopeRule.spec.ts | 60 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/templateAccessibilityTableScopeRule.ts create mode 100644 test/templateAccessibilityTableScopeRule.spec.ts diff --git a/src/index.ts b/src/index.ts index 8ec6f2cc7..ba6305ea1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,7 @@ export { Rule as TemplateAccessibilityValidAriaRule } from './templateAccessibil export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule'; export { Rule as TemplateClickEventsHaveKeyEventsRule } from './templateClickEventsHaveKeyEventsRule'; export { Rule as TemplateAccessibilityAltTextRule } from './templateAccessibilityAltTextRule'; +export { Rule as TemplateAccessibilityTableScopeRule } from './templateAccessibilityTableScopeRule'; export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule'; export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule'; export { Rule as TrackByFunctionRule } from './trackByFunctionRule'; diff --git a/src/templateAccessibilityTableScopeRule.ts b/src/templateAccessibilityTableScopeRule.ts new file mode 100644 index 000000000..0d2344722 --- /dev/null +++ b/src/templateAccessibilityTableScopeRule.ts @@ -0,0 +1,55 @@ +import { ElementAst } from '@angular/compiler'; +import { IRuleMetadata, RuleFailure, Rules, Utils } from 'tslint/lib'; +import { SourceFile } from 'typescript'; +import { NgWalker } from './angular/ngWalker'; +import { BasicTemplateAstVisitor } from './angular'; + +class TemplateAccessibilityTableScopeVisitor extends BasicTemplateAstVisitor { + visitElement(ast: ElementAst, context: any) { + this.validateElement(ast); + super.visitElement(ast, context); + } + + validateElement(element: ElementAst) { + if (element.name === 'th') { + return; + } + + const hasScopeInput = element.inputs.some(input => input.name === 'scope'); + const hasScopeAttr = element.attrs.some(attr => attr.name === 'scope'); + if (hasScopeInput || hasScopeAttr) { + const { + sourceSpan: { + end: { offset: endOffset }, + start: { offset: startOffset } + } + } = element; + this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_MESSAGE); + } + } +} + +export class Rule extends Rules.AbstractRule { + static readonly metadata: IRuleMetadata = { + description: 'Ensures that scope is not used on any element except th', + options: null, + optionsDescription: 'Not configurable.', + rationale: Utils.dedent` + The scope attribute makes table navigation much easier for screen reader users, provided that it is used correctly. + If used incorrectly, it can make table navigation much harder and less efficient. (aXe) + `, + ruleName: 'template-accessibility-table-scope', + type: 'functionality', + typescriptOnly: true + }; + + static readonly FAILURE_MESSAGE = 'Scope attribute can only be on element'; + + apply(sourceFile: SourceFile): RuleFailure[] { + return this.applyWithWalker( + new NgWalker(sourceFile, this.getOptions(), { + templateVisitorCtrl: TemplateAccessibilityTableScopeVisitor + }) + ); + } +} diff --git a/test/templateAccessibilityTableScopeRule.spec.ts b/test/templateAccessibilityTableScopeRule.spec.ts new file mode 100644 index 000000000..cca9f4d04 --- /dev/null +++ b/test/templateAccessibilityTableScopeRule.spec.ts @@ -0,0 +1,60 @@ +import { Rule } from '../src/templateAccessibilityTableScopeRule'; +import { assertAnnotated, assertSuccess } from './testHelper'; + +const { + FAILURE_MESSAGE, + metadata: { ruleName } +} = Rule; + +describe(ruleName, () => { + describe('failure', () => { + it('should fail when element other than th has scope', () => { + const source = ` + @Component({ + template: \` +
+ ~~~~~~~~~~~ + \` + }) + class Bar {} + `; + assertAnnotated({ + message: FAILURE_MESSAGE, + ruleName, + source + }); + }); + + it('should fail when element other than th has scope input', () => { + const source = ` + @Component({ + template: \` +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~ + \` + }) + class Bar {} + `; + assertAnnotated({ + message: FAILURE_MESSAGE, + ruleName, + source + }); + }); + }); + + describe('success', () => { + it('should work when th has scope', () => { + const source = ` + @Component({ + template: \` + + + \` + }) + class Bar {} + `; + assertSuccess(ruleName, source); + }); + }); +});