Skip to content

Commit 6ff8c56

Browse files
mohammedzamakhanmgechev
authored andcommitted
feat(rule): anchor element should have content (#742)
1 parent 76c24fa commit 6ff8c56

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export { Rule as TemplateConditionalComplexityRule } from './templateConditional
3030
export { Rule as TemplateCyclomaticComplexityRule } from './templateCyclomaticComplexityRule';
3131
export { Rule as TemplateAccessibilityTabindexNoPositiveRule } from './templateAccessibilityTabindexNoPositiveRule';
3232
export { Rule as TemplateAccessibilityLabelForVisitor } from './templateAccessibilityLabelForRule';
33+
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
3334
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
3435
export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule';
3536
export { Rule as TrackByFunctionRule } from './trackByFunctionRule';
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 TemplateAccessibilityAnchorContentVisitor 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 !== 'a') {
15+
return;
16+
}
17+
18+
const hasContent = element.children.length;
19+
const hasInnerContent = element.inputs.some(input => input.name === 'innerHTML' || input.name === 'innerText');
20+
if (hasContent || hasInnerContent) {
21+
return;
22+
}
23+
const {
24+
sourceSpan: {
25+
end: { offset: endOffset },
26+
start: { offset: startOffset }
27+
}
28+
} = element;
29+
this.addFailureFromStartToEnd(startOffset, endOffset, Rule.FAILURE_MESSAGE);
30+
}
31+
}
32+
33+
export class Rule extends Rules.AbstractRule {
34+
static readonly metadata: IRuleMetadata = {
35+
description: 'Ensures that the anchor element has some content in it',
36+
options: null,
37+
optionsDescription: 'Not configurable.',
38+
rationale: 'Anchor elements should have content to be accessible by screen readers',
39+
ruleName: 'template-accessibility-anchor-content',
40+
type: 'functionality',
41+
typescriptOnly: true
42+
};
43+
44+
static readonly FAILURE_MESSAGE = 'Anchor element should have content';
45+
46+
apply(sourceFile: SourceFile): RuleFailure[] {
47+
return this.applyWithWalker(
48+
new NgWalker(sourceFile, this.getOptions(), {
49+
templateVisitorCtrl: TemplateAccessibilityAnchorContentVisitor
50+
})
51+
);
52+
}
53+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Rule } from '../src/templateAccessibilityAnchorContentRule';
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 with no content in anchor tag', () => {
12+
const source = `
13+
@Component({
14+
template: \`
15+
<a href="#" [routerLink]="['route1']"></a>
16+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17+
\`
18+
})
19+
class Bar {}
20+
`;
21+
assertAnnotated({
22+
message: FAILURE_MESSAGE,
23+
ruleName,
24+
source
25+
});
26+
});
27+
});
28+
29+
describe('success', () => {
30+
it('should work when anchor has any kind of content in it', () => {
31+
const source = `
32+
@Component({
33+
template: \`
34+
<a>Anchor Content!</a>
35+
<a><app-content></app-content></a>
36+
<a [innerHTML]="dangerouslySetHTML"></a>
37+
<a [innerText]="text"></a>
38+
\`
39+
})
40+
class Bar {}
41+
`;
42+
assertSuccess(ruleName, source);
43+
});
44+
});
45+
});

0 commit comments

Comments
 (0)