From 799382fd1b16bbff9bfb1587b643c4125df8f393 Mon Sep 17 00:00:00 2001 From: Zama Khan Mohammed Date: Mon, 11 Feb 2019 12:19:20 -0600 Subject: [PATCH] feat(rule): autofocus attribute should not be used (#749) --- src/index.ts | 1 + src/templateNoAutofocusRule.ts | 51 +++++++++++++++++++++++ test/templateNoAutofocusRule.spec.ts | 62 ++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 src/templateNoAutofocusRule.ts create mode 100644 test/templateNoAutofocusRule.spec.ts diff --git a/src/index.ts b/src/index.ts index fa7c2fc8b..d391bb7c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ export { Rule as TemplateConditionalComplexityRule } from './templateConditional export { Rule as TemplateCyclomaticComplexityRule } from './templateCyclomaticComplexityRule'; export { Rule as TemplateAccessibilityTabindexNoPositiveRule } from './templateAccessibilityTabindexNoPositiveRule'; export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule'; +export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule'; export { Rule as TrackByFunctionRule } from './trackByFunctionRule'; export { Rule as UseHostPropertyDecoratorRule } from './useHostPropertyDecoratorRule'; export { Rule as UseInputPropertyDecoratorRule } from './useInputPropertyDecoratorRule'; diff --git a/src/templateNoAutofocusRule.ts b/src/templateNoAutofocusRule.ts new file mode 100644 index 000000000..1304647f0 --- /dev/null +++ b/src/templateNoAutofocusRule.ts @@ -0,0 +1,51 @@ +import { AttrAst, BoundElementPropertyAst } from '@angular/compiler'; +import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib'; +import { SourceFile } from 'typescript/lib/typescript'; +import { NgWalker } from './angular/ngWalker'; +import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor'; + +export class Rule extends Rules.AbstractRule { + static readonly metadata: IRuleMetadata = { + description: 'Ensure that autofocus property is not used', + options: null, + optionsDescription: 'Not configurable.', + rationale: 'autofocus attribute reduces usability and accessibility for users.', + ruleName: 'template-no-autofocus', + type: 'functionality', + typescriptOnly: true + }; + + static readonly FAILURE_STRING = 'autofocus attribute should not be used, as it reduces usability and accessibility for users.'; + + apply(sourceFile: SourceFile): RuleFailure[] { + return this.applyWithWalker( + new NgWalker(sourceFile, this.getOptions(), { + templateVisitorCtrl: TemplateConditionalComplexityVisitor + }) + ); + } +} + +class TemplateConditionalComplexityVisitor extends BasicTemplateAstVisitor { + visitAttr(ast: AttrAst, context: any) { + this.validateAttribute(ast); + super.visitAttr(ast, context); + } + + visitElementProperty(ast: BoundElementPropertyAst) { + this.validateAttribute(ast); + super.visitElementProperty(ast, context); + } + + validateAttribute(ast: AttrAst | BoundElementPropertyAst) { + if (ast.name === 'autofocus') { + const { + sourceSpan: { + end: { offset: endOffset }, + start: { offset: startOffset } + } + } = ast; + this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_STRING); + } + } +} diff --git a/test/templateNoAutofocusRule.spec.ts b/test/templateNoAutofocusRule.spec.ts new file mode 100644 index 000000000..ab612db40 --- /dev/null +++ b/test/templateNoAutofocusRule.spec.ts @@ -0,0 +1,62 @@ +import { assertAnnotated, assertMultipleAnnotated, assertSuccess } from './testHelper'; +import { Rule } from '../src/templateNoAutofocusRule'; + +const { + FAILURE_STRING, + metadata: { ruleName } +} = Rule; + +describe(ruleName, () => { + describe('failure', () => { + it('should fail if autofocus attribute is used', () => { + const source = ` + @Component({ + selector: 'test', + template: '
Autofocus
' + ~~~~~~~~~ + }) + class Test { + constructor(foo: Observable) {} + } + `; + assertAnnotated({ + message: FAILURE_STRING, + ruleName: ruleName, + source + }); + }); + + it('should fail if autofocus input is used', () => { + const source = ` + @Component({ + selector: 'test', + template: '
Autofocus
' + ~~~~~~~~~~~~~~~~~~~~~~~~ + }) + class Test { + constructor(foo: Observable) {} + } + `; + assertAnnotated({ + message: FAILURE_STRING, + ruleName: ruleName, + source + }); + }); + }); + + describe('success', () => { + it('should succeed if autofocus is not used', () => { + const source = ` + @Component({ + selector: 'test', + template: '
No Autofocus
' + }) + class Test { + constructor(foo: Observable) {} + } + `; + assertSuccess(ruleName, source); + }); + }); +});