Skip to content

Commit

Permalink
feat(rule): anchor element should have content (#742)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammedzamakhan authored and mgechev committed Feb 11, 2019
1 parent 76c24fa commit 6ff8c56
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -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 TemplateAccessibilityLabelForVisitor } from './templateAccessibilityLabelForRule';
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule';
export { Rule as TrackByFunctionRule } from './trackByFunctionRule';
Expand Down
53 changes: 53 additions & 0 deletions src/templateAccessibilityAnchorContentRule.ts
@@ -0,0 +1,53 @@
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 TemplateAccessibilityAnchorContentVisitor extends BasicTemplateAstVisitor {
visitElement(ast: ElementAst, context: any) {
this.validateElement(ast);
super.visitElement(ast, context);
}

validateElement(element: ElementAst) {
if (element.name !== 'a') {
return;
}

const hasContent = element.children.length;
const hasInnerContent = element.inputs.some(input => input.name === 'innerHTML' || input.name === 'innerText');
if (hasContent || hasInnerContent) {
return;
}
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 the anchor element has some content in it',
options: null,
optionsDescription: 'Not configurable.',
rationale: 'Anchor elements should have content to be accessible by screen readers',
ruleName: 'template-accessibility-anchor-content',
type: 'functionality',
typescriptOnly: true
};

static readonly FAILURE_MESSAGE = 'Anchor element should have content';

apply(sourceFile: SourceFile): RuleFailure[] {
return this.applyWithWalker(
new NgWalker(sourceFile, this.getOptions(), {
templateVisitorCtrl: TemplateAccessibilityAnchorContentVisitor
})
);
}
}
45 changes: 45 additions & 0 deletions test/templateAccessibilityAnchorContentRule.spec.ts
@@ -0,0 +1,45 @@
import { Rule } from '../src/templateAccessibilityAnchorContentRule';
import { assertAnnotated, assertSuccess } from './testHelper';

const {
FAILURE_MESSAGE,
metadata: { ruleName }
} = Rule;

describe(ruleName, () => {
describe('failure', () => {
it('should fail with no content in anchor tag', () => {
const source = `
@Component({
template: \`
<a href="#" [routerLink]="['route1']"></a>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\`
})
class Bar {}
`;
assertAnnotated({
message: FAILURE_MESSAGE,
ruleName,
source
});
});
});

describe('success', () => {
it('should work when anchor has any kind of content in it', () => {
const source = `
@Component({
template: \`
<a>Anchor Content!</a>
<a><app-content></app-content></a>
<a [innerHTML]="dangerouslySetHTML"></a>
<a [innerText]="text"></a>
\`
})
class Bar {}
`;
assertSuccess(ruleName, source);
});
});
});

0 comments on commit 6ff8c56

Please sign in to comment.