Skip to content

Commit 2832615

Browse files
mohammedzamakhanmgechev
authored andcommitted
feat(rule): only th element can have scope (#743)
1 parent b0b330f commit 2832615

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export { Rule as TemplateAccessibilityValidAriaRule } from './templateAccessibil
3535
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
3636
export { Rule as TemplateClickEventsHaveKeyEventsRule } from './templateClickEventsHaveKeyEventsRule';
3737
export { Rule as TemplateAccessibilityAltTextRule } from './templateAccessibilityAltTextRule';
38+
export { Rule as TemplateAccessibilityTableScopeRule } from './templateAccessibilityTableScopeRule';
3839
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
3940
export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule';
4041
export { Rule as TrackByFunctionRule } from './trackByFunctionRule';
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ElementAst } from '@angular/compiler';
2+
import { IRuleMetadata, RuleFailure, Rules, Utils } from 'tslint/lib';
3+
import { SourceFile } from 'typescript';
4+
import { NgWalker } from './angular/ngWalker';
5+
import { BasicTemplateAstVisitor } from './angular';
6+
7+
class TemplateAccessibilityTableScopeVisitor extends BasicTemplateAstVisitor {
8+
visitElement(ast: ElementAst, context: any) {
9+
this.validateElement(ast);
10+
super.visitElement(ast, context);
11+
}
12+
13+
validateElement(element: ElementAst) {
14+
if (element.name === 'th') {
15+
return;
16+
}
17+
18+
const hasScopeInput = element.inputs.some(input => input.name === 'scope');
19+
const hasScopeAttr = element.attrs.some(attr => attr.name === 'scope');
20+
if (hasScopeInput || hasScopeAttr) {
21+
const {
22+
sourceSpan: {
23+
end: { offset: endOffset },
24+
start: { offset: startOffset }
25+
}
26+
} = element;
27+
this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_MESSAGE);
28+
}
29+
}
30+
}
31+
32+
export class Rule extends Rules.AbstractRule {
33+
static readonly metadata: IRuleMetadata = {
34+
description: 'Ensures that scope is not used on any element except th',
35+
options: null,
36+
optionsDescription: 'Not configurable.',
37+
rationale: Utils.dedent`
38+
The scope attribute makes table navigation much easier for screen reader users, provided that it is used correctly.
39+
If used incorrectly, it can make table navigation much harder and less efficient. (aXe)
40+
`,
41+
ruleName: 'template-accessibility-table-scope',
42+
type: 'functionality',
43+
typescriptOnly: true
44+
};
45+
46+
static readonly FAILURE_MESSAGE = 'Scope attribute can only be on <th> element';
47+
48+
apply(sourceFile: SourceFile): RuleFailure[] {
49+
return this.applyWithWalker(
50+
new NgWalker(sourceFile, this.getOptions(), {
51+
templateVisitorCtrl: TemplateAccessibilityTableScopeVisitor
52+
})
53+
);
54+
}
55+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Rule } from '../src/templateAccessibilityTableScopeRule';
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 element other than th has scope', () => {
12+
const source = `
13+
@Component({
14+
template: \`
15+
<div scope></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 element other than th has scope input', () => {
29+
const source = `
30+
@Component({
31+
template: \`
32+
<div [attr.scope]="scope"></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 when th has scope', () => {
48+
const source = `
49+
@Component({
50+
template: \`
51+
<th scope="col"></th>
52+
<th [attr.scope]="col"></th>
53+
\`
54+
})
55+
class Bar {}
56+
`;
57+
assertSuccess(ruleName, source);
58+
});
59+
});
60+
});

0 commit comments

Comments
 (0)