Skip to content

Commit 43902f7

Browse files
mohammedzamakhanmgechev
authored andcommitted
feat(rule): tabindex should not be positive (#744)
1 parent e7b2fa7 commit 43902f7

File tree

4 files changed

+127
-0
lines changed

4 files changed

+127
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export { Rule as PreferInlineDecorator } from './preferInlineDecoratorRule';
2828
export { Rule as PreferOutputReadonlyRule } from './preferOutputReadonlyRule';
2929
export { Rule as TemplateConditionalComplexityRule } from './templateConditionalComplexityRule';
3030
export { Rule as TemplateCyclomaticComplexityRule } from './templateCyclomaticComplexityRule';
31+
export { Rule as TemplateAccessibilityTabindexNoPositiveRule } from './templateAccessibilityTabindexNoPositiveRule';
3132
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
3233
export { Rule as TrackByFunctionRule } from './trackByFunctionRule';
3334
export { Rule as UseHostPropertyDecoratorRule } from './useHostPropertyDecoratorRule';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ElementAst } from '@angular/compiler';
2+
import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib';
3+
import { SourceFile } from 'typescript/lib/typescript';
4+
import { NgWalker } from './angular/ngWalker';
5+
import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor';
6+
import { getAttributeValue } from './util/getAttributeValue';
7+
8+
export class Rule extends Rules.AbstractRule {
9+
static readonly metadata: IRuleMetadata = {
10+
description: 'Ensures that the tab index is not positive',
11+
options: null,
12+
optionsDescription: 'Not configurable.',
13+
rationale: 'positive values for tabidex attribute should be avoided because they mess up with the order of focus (AX_FOCUS_03)',
14+
ruleName: 'template-accessibility-tabindex-no-positive',
15+
type: 'functionality',
16+
typescriptOnly: true
17+
};
18+
19+
static readonly FAILURE_MESSAGE = 'Tabindex cannot be positive';
20+
21+
apply(sourceFile: SourceFile): RuleFailure[] {
22+
return this.applyWithWalker(
23+
new NgWalker(sourceFile, this.getOptions(), {
24+
templateVisitorCtrl: TemplateAccessibilityTabindexNoPositiveVisitor
25+
})
26+
);
27+
}
28+
}
29+
30+
class TemplateAccessibilityTabindexNoPositiveVisitor extends BasicTemplateAstVisitor {
31+
visitElement(ast: ElementAst, context: any): any {
32+
this.validateElement(ast);
33+
super.visitElement(ast, context);
34+
}
35+
36+
private validateElement(element: ElementAst) {
37+
let tabIndexValue = getAttributeValue(element, 'tabindex');
38+
if (tabIndexValue) {
39+
tabIndexValue = parseInt(tabIndexValue, 10);
40+
if (tabIndexValue > 0) {
41+
const {
42+
sourceSpan: {
43+
end: { offset: endOffset },
44+
start: { offset: startOffset }
45+
}
46+
} = element;
47+
this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_MESSAGE);
48+
}
49+
}
50+
}
51+
}

src/util/getAttributeValue.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ElementAst } from '@angular/compiler';
2+
3+
export const getAttributeValue = (element: ElementAst, property: string) => {
4+
const attr = element.attrs.find(attr => attr.name === property);
5+
const input = element.inputs.find(input => input.name === property);
6+
if (attr) {
7+
return attr.value;
8+
}
9+
if (input) {
10+
return (<any>input.value).ast.value;
11+
}
12+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Rule } from '../src/templateAccessibilityTabindexNoPositiveRule';
2+
import { assertAnnotated, assertSuccess } from './testHelper';
3+
4+
const {
5+
FAILURE_MESSAGE,
6+
metadata: { ruleName }
7+
} = Rule;
8+
9+
describe(ruleName, () => {
10+
describe('failure', () => {
11+
it('should fail when tabindex attr is positive', () => {
12+
const source = `
13+
@Component({
14+
template: \`
15+
<div tabindex="5"></div>
16+
~~~~~~~~~~~~~~~~~~
17+
\`
18+
})
19+
class Bar {}
20+
`;
21+
assertAnnotated({
22+
message: FAILURE_MESSAGE,
23+
ruleName,
24+
source
25+
});
26+
});
27+
28+
it('should fail when tabindex input is positive', () => {
29+
const source = `
30+
@Component({
31+
template: \`
32+
<div [attr.tabindex]="1"></div>
33+
~~~~~~~~~~~~~~~~~~~~~~~~~
34+
\`
35+
})
36+
class Bar {}
37+
`;
38+
assertAnnotated({
39+
message: FAILURE_MESSAGE,
40+
ruleName,
41+
source
42+
});
43+
});
44+
});
45+
46+
describe('success', () => {
47+
it('should work with tab index is not positive', () => {
48+
const source = `
49+
@Component({
50+
template: \`
51+
<span tabindex="-1"></span>
52+
<span tabindex="0"></span>
53+
<span [attr.tabindex]="-1"></span>
54+
<span [attr.tabindex]="0"></span>
55+
<span [attr.tabindex]="tabIndex"></span>
56+
\`
57+
})
58+
class Bar {}
59+
`;
60+
assertSuccess(ruleName, source);
61+
});
62+
});
63+
});

0 commit comments

Comments
 (0)