-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rule): only th element can have scope (#743)
- Loading branch information
1 parent
b0b330f
commit 2832615
Showing
3 changed files
with
116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); | ||
}); |