Skip to content

Commit

Permalink
feat(rule): only th element can have scope (#743)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammedzamakhan authored and mgechev committed Feb 13, 2019
1 parent b0b330f commit 2832615
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -35,6 +35,7 @@ export { Rule as TemplateAccessibilityValidAriaRule } from './templateAccessibil
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
export { Rule as TemplateClickEventsHaveKeyEventsRule } from './templateClickEventsHaveKeyEventsRule';
export { Rule as TemplateAccessibilityAltTextRule } from './templateAccessibilityAltTextRule';
export { Rule as TemplateAccessibilityTableScopeRule } from './templateAccessibilityTableScopeRule';
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
export { Rule as TemplateNoAutofocusRule } from './templateNoAutofocusRule';
export { Rule as TrackByFunctionRule } from './trackByFunctionRule';
Expand Down
55 changes: 55 additions & 0 deletions src/templateAccessibilityTableScopeRule.ts
@@ -0,0 +1,55 @@
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 TemplateAccessibilityTableScopeVisitor extends BasicTemplateAstVisitor {
visitElement(ast: ElementAst, context: any) {
this.validateElement(ast);
super.visitElement(ast, context);
}

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

const hasScopeInput = element.inputs.some(input => input.name === 'scope');
const hasScopeAttr = element.attrs.some(attr => attr.name === 'scope');
if (hasScopeInput || hasScopeAttr) {
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 scope is not used on any element except th',
options: null,
optionsDescription: 'Not configurable.',
rationale: Utils.dedent`
The scope attribute makes table navigation much easier for screen reader users, provided that it is used correctly.
If used incorrectly, it can make table navigation much harder and less efficient. (aXe)
`,
ruleName: 'template-accessibility-table-scope',
type: 'functionality',
typescriptOnly: true
};

static readonly FAILURE_MESSAGE = 'Scope attribute can only be on <th> element';

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

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

describe(ruleName, () => {
describe('failure', () => {
it('should fail when element other than th has scope', () => {
const source = `
@Component({
template: \`
<div scope></div>
~~~~~~~~~~~
\`
})
class Bar {}
`;
assertAnnotated({
message: FAILURE_MESSAGE,
ruleName,
source
});
});

it('should fail when element other than th has scope input', () => {
const source = `
@Component({
template: \`
<div [attr.scope]="scope"></div>
~~~~~~~~~~~~~~~~~~~~~~~~~~
\`
})
class Bar {}
`;
assertAnnotated({
message: FAILURE_MESSAGE,
ruleName,
source
});
});
});

describe('success', () => {
it('should work when th has scope', () => {
const source = `
@Component({
template: \`
<th scope="col"></th>
<th [attr.scope]="col"></th>
\`
})
class Bar {}
`;
assertSuccess(ruleName, source);
});
});
});

0 comments on commit 2832615

Please sign in to comment.