forked from angular-eslint/angular-eslint
/
accessibility-elements-content.ts
82 lines (75 loc) · 2.27 KB
/
accessibility-elements-content.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import type { TmplAstElement } from '@angular-eslint/bundled-angular-compiler';
import { getTemplateParserServices } from '@angular-eslint/utils';
import { createESLintRule } from '../utils/create-eslint-rule';
import { isHiddenFromScreenReader } from '../utils/is-hidden-from-screen-reader';
type Options = [
{
readonly allowList?: readonly string[];
},
];
export type MessageIds = 'accessibilityElementsContent';
export const RULE_NAME = 'accessibility-elements-content';
const DEFAULT_SAFELIST_ATTRIBUTES: readonly string[] = [
'aria-label',
'innerHtml',
'innerHTML',
'innerText',
'outerHTML',
'title',
];
const DEFAULT_OPTIONS: Options[0] = {
allowList: DEFAULT_SAFELIST_ATTRIBUTES,
};
export default createESLintRule<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description:
'Ensures that the heading, anchor and button elements have content in it',
recommended: false,
},
schema: [
{
additionalProperties: false,
properties: {
allowList: {
items: { type: 'string' },
type: 'array',
uniqueItems: true,
},
},
type: 'object',
},
],
messages: {
accessibilityElementsContent: '<{{element}}> should have content',
},
},
defaultOptions: [DEFAULT_OPTIONS],
create(context, [{ allowList }]) {
const parserServices = getTemplateParserServices(context);
return {
'Element$1[name=/^(a|button|h1|h2|h3|h4|h5|h6)$/][children.length=0]'(
node: TmplAstElement,
) {
if (isHiddenFromScreenReader(node)) return;
const { attributes, inputs, name: element, sourceSpan } = node;
const safelistAttributes: ReadonlySet<string> = new Set([
...DEFAULT_SAFELIST_ATTRIBUTES,
...(allowList ?? []),
]);
const hasAttributeSafelisted = [...attributes, ...inputs]
.map(({ name }) => name)
.some((inputName) => safelistAttributes.has(inputName));
if (hasAttributeSafelisted) return;
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
context.report({
loc,
messageId: 'accessibilityElementsContent',
data: { element },
});
},
};
},
});