@angular-eslint/eslint-plugin-template 15.2.2-alpha.24 → 15.2.2-alpha.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configs/README.md +10 -0
- package/dist/configs/accessibility.json +17 -0
- package/dist/configs/all.json +10 -10
- package/dist/configs/recommended.json +2 -1
- package/dist/index.js +95 -1
- package/dist/processors.js +210 -0
- package/dist/rules/alt-text.js +131 -0
- package/dist/rules/attributes-order.js +233 -0
- package/dist/rules/banana-in-box.js +47 -0
- package/dist/rules/button-has-type.js +76 -0
- package/dist/rules/click-events-have-key-events.js +57 -0
- package/dist/rules/conditional-complexity.js +107 -0
- package/dist/rules/cyclomatic-complexity.js +50 -0
- package/dist/rules/elements-content.js +70 -0
- package/dist/rules/eqeqeq.js +99 -0
- package/dist/rules/i18n.js +368 -0
- package/dist/rules/interactive-supports-focus.js +64 -0
- package/dist/rules/label-has-associated-control.js +103 -0
- package/dist/rules/mouse-events-have-key-events.js +40 -0
- package/dist/rules/no-any.js +59 -0
- package/dist/rules/no-autofocus.js +41 -0
- package/dist/rules/no-call-expression.js +68 -0
- package/dist/rules/no-distracting-elements.js +36 -0
- package/dist/rules/no-duplicate-attributes.js +97 -0
- package/dist/rules/no-inline-styles.js +104 -0
- package/dist/rules/no-interpolation-in-attributes.js +36 -0
- package/dist/rules/no-negated-async.js +60 -0
- package/dist/rules/no-positive-tabindex.js +43 -0
- package/dist/rules/role-has-required-aria.js +73 -0
- package/dist/rules/table-scope.js +41 -0
- package/dist/rules/use-track-by-function.js +56 -0
- package/dist/rules/valid-aria.js +126 -0
- package/dist/utils/attributes-comparator.js +19 -0
- package/dist/utils/constants.js +11 -0
- package/dist/utils/create-eslint-rule.js +26 -0
- package/dist/utils/get-attribute-value.js +39 -0
- package/dist/utils/get-dom-elements.js +9 -0
- package/dist/utils/get-nearest-node-from.js +17 -0
- package/dist/utils/get-original-attribute-name.js +29 -0
- package/dist/utils/is-child-node-of.js +12 -0
- package/dist/utils/is-content-editable.js +14 -0
- package/dist/utils/is-disabled-element.js +20 -0
- package/dist/utils/is-hidden-from-screen-reader.js +97 -0
- package/dist/utils/is-interactive-element/get-interactive-element-ax-object-schemas.js +27 -0
- package/dist/utils/is-interactive-element/get-interactive-element-role-schemas.js +38 -0
- package/dist/utils/is-interactive-element/get-non-interactive-element-role-schemas.js +44 -0
- package/dist/utils/is-interactive-element/index.js +45 -0
- package/dist/utils/is-presentation-role.js +14 -0
- package/dist/utils/is-semantic-role-element.js +36 -0
- package/dist/utils/to-pattern.js +7 -0
- package/package.json +6 -6
- package/dist/configs/base.json +0 -4
- package/dist/eslint-plugin-template/src/index.d.ts +0 -133
- package/dist/eslint-plugin-template/src/processors.d.ts +0 -37
- package/dist/eslint-plugin-template/src/rules/accessibility-alt-text.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/accessibility-elements-content.d.ts +0 -9
- package/dist/eslint-plugin-template/src/rules/accessibility-interactive-supports-focus.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/accessibility-label-for.d.ts +0 -11
- package/dist/eslint-plugin-template/src/rules/accessibility-label-has-associated-control.d.ts +0 -14
- package/dist/eslint-plugin-template/src/rules/accessibility-role-has-required-aria.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/accessibility-table-scope.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/accessibility-valid-aria.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/attributes-order.d.ts +0 -18
- package/dist/eslint-plugin-template/src/rules/banana-in-box.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/button-has-type.d.ts +0 -5
- package/dist/eslint-plugin-template/src/rules/click-events-have-key-events.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/conditional-complexity.d.ts +0 -7
- package/dist/eslint-plugin-template/src/rules/cyclomatic-complexity.d.ts +0 -7
- package/dist/eslint-plugin-template/src/rules/eqeqeq.d.ts +0 -8
- package/dist/eslint-plugin-template/src/rules/i18n.d.ts +0 -18
- package/dist/eslint-plugin-template/src/rules/mouse-events-have-key-events.d.ts +0 -5
- package/dist/eslint-plugin-template/src/rules/no-any.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/no-autofocus.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/no-call-expression.d.ts +0 -9
- package/dist/eslint-plugin-template/src/rules/no-distracting-elements.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/no-duplicate-attributes.d.ts +0 -10
- package/dist/eslint-plugin-template/src/rules/no-inline-styles.d.ts +0 -10
- package/dist/eslint-plugin-template/src/rules/no-interpolation-in-attributes.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/no-negated-async.d.ts +0 -7
- package/dist/eslint-plugin-template/src/rules/no-positive-tabindex.d.ts +0 -4
- package/dist/eslint-plugin-template/src/rules/use-track-by-function.d.ts +0 -4
- package/dist/eslint-plugin-template/src/utils/attributes-comparator.d.ts +0 -3
- package/dist/eslint-plugin-template/src/utils/constants.d.ts +0 -8
- package/dist/eslint-plugin-template/src/utils/create-eslint-rule.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/get-attribute-value.d.ts +0 -12
- package/dist/eslint-plugin-template/src/utils/get-dom-elements.d.ts +0 -1
- package/dist/eslint-plugin-template/src/utils/get-nearest-node-from.d.ts +0 -6
- package/dist/eslint-plugin-template/src/utils/get-original-attribute-name.d.ts +0 -13
- package/dist/eslint-plugin-template/src/utils/is-child-node-of.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/is-content-editable.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/is-disabled-element.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/is-hidden-from-screen-reader.d.ts +0 -9
- package/dist/eslint-plugin-template/src/utils/is-interactive-element/get-interactive-element-ax-object-schemas.d.ts +0 -9
- package/dist/eslint-plugin-template/src/utils/is-interactive-element/get-interactive-element-role-schemas.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/is-interactive-element/get-non-interactive-element-role-schemas.d.ts +0 -3
- package/dist/eslint-plugin-template/src/utils/is-interactive-element/index.d.ts +0 -9
- package/dist/eslint-plugin-template/src/utils/is-presentation-role.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/is-semantic-role-element.d.ts +0 -2
- package/dist/eslint-plugin-template/src/utils/to-pattern.d.ts +0 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
const get_dom_elements_1 = require("../utils/get-dom-elements");
|
|
7
|
+
const to_pattern_1 = require("../utils/to-pattern");
|
|
8
|
+
exports.RULE_NAME = 'no-positive-tabindex';
|
|
9
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
10
|
+
name: exports.RULE_NAME,
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Ensures that the `tabindex` attribute is not positive',
|
|
15
|
+
recommended: false,
|
|
16
|
+
},
|
|
17
|
+
hasSuggestions: true,
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
noPositiveTabindex: 'The `tabindex` attribute should not be positive',
|
|
21
|
+
suggestNonNegativeTabindex: 'Use `tabindex="{{tabindex}}"`',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
27
|
+
const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
|
|
28
|
+
return {
|
|
29
|
+
[`Element$1[name=${elementNamePattern}] > BoundAttribute[name="tabindex"][value.ast.value>0], TextAttribute[name="tabindex"][value>0]`]({ valueSpan, }) {
|
|
30
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(valueSpan);
|
|
31
|
+
context.report({
|
|
32
|
+
loc,
|
|
33
|
+
messageId: 'noPositiveTabindex',
|
|
34
|
+
suggest: ['-1', '0'].map((tabindex) => ({
|
|
35
|
+
messageId: 'suggestNonNegativeTabindex',
|
|
36
|
+
fix: (fixer) => fixer.replaceTextRange([valueSpan.start.offset, valueSpan.end.offset], tabindex),
|
|
37
|
+
data: { tabindex },
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const aria_query_1 = require("aria-query");
|
|
6
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
7
|
+
const get_dom_elements_1 = require("../utils/get-dom-elements");
|
|
8
|
+
const to_pattern_1 = require("../utils/to-pattern");
|
|
9
|
+
const is_semantic_role_element_1 = require("../utils/is-semantic-role-element");
|
|
10
|
+
exports.RULE_NAME = 'role-has-required-aria';
|
|
11
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
12
|
+
name: exports.RULE_NAME,
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
docs: {
|
|
16
|
+
description: '[Accessibility] Ensures elements with ARIA roles have all required properties for that role.',
|
|
17
|
+
recommended: false,
|
|
18
|
+
},
|
|
19
|
+
hasSuggestions: true,
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
roleHasRequiredAria: 'The {{element}} with role="{{role}}" does not have required ARIA properties: {{missingProps}}',
|
|
23
|
+
suggestRemoveRole: 'Remove role `{{role}}`',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
create(context) {
|
|
28
|
+
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
29
|
+
const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
|
|
30
|
+
return {
|
|
31
|
+
[`Element$1[name=${elementNamePattern}] > TextAttribute[name='role']`](node) {
|
|
32
|
+
const { value: role, sourceSpan } = node;
|
|
33
|
+
const { attributes, inputs, name: element } = node.parent;
|
|
34
|
+
const props = [...attributes, ...inputs];
|
|
35
|
+
const roleDef = aria_query_1.roles.get(role);
|
|
36
|
+
const requiredProps = Object.keys((roleDef === null || roleDef === void 0 ? void 0 : roleDef.requiredProps) || {});
|
|
37
|
+
if (!requiredProps.length)
|
|
38
|
+
return;
|
|
39
|
+
if ((0, is_semantic_role_element_1.isSemanticRoleElement)(element, role, props))
|
|
40
|
+
return;
|
|
41
|
+
const missingProps = requiredProps
|
|
42
|
+
.filter((requiredProp) => !props.find((prop) => prop.name === requiredProp))
|
|
43
|
+
.join(', ');
|
|
44
|
+
if (missingProps) {
|
|
45
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
46
|
+
context.report({
|
|
47
|
+
loc,
|
|
48
|
+
messageId: 'roleHasRequiredAria',
|
|
49
|
+
data: {
|
|
50
|
+
element,
|
|
51
|
+
role,
|
|
52
|
+
missingProps,
|
|
53
|
+
},
|
|
54
|
+
suggest: [
|
|
55
|
+
{
|
|
56
|
+
messageId: 'suggestRemoveRole',
|
|
57
|
+
data: {
|
|
58
|
+
element,
|
|
59
|
+
role,
|
|
60
|
+
missingProps,
|
|
61
|
+
},
|
|
62
|
+
fix: (fixer) => fixer.removeRange([
|
|
63
|
+
(sourceSpan === null || sourceSpan === void 0 ? void 0 : sourceSpan.start.offset) - 1,
|
|
64
|
+
sourceSpan === null || sourceSpan === void 0 ? void 0 : sourceSpan.end.offset,
|
|
65
|
+
]),
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
const get_dom_elements_1 = require("../utils/get-dom-elements");
|
|
7
|
+
const to_pattern_1 = require("../utils/to-pattern");
|
|
8
|
+
exports.RULE_NAME = 'table-scope';
|
|
9
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
10
|
+
name: exports.RULE_NAME,
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: {
|
|
14
|
+
description: '[Accessibility] Ensures that the `scope` attribute is only used on the `<th>` element',
|
|
15
|
+
recommended: false,
|
|
16
|
+
},
|
|
17
|
+
fixable: 'code',
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
tableScope: 'The `scope` attribute should only be on the `<th>` element',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultOptions: [],
|
|
24
|
+
create(context) {
|
|
25
|
+
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
26
|
+
const domElementsPattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()].filter((domElement) => domElement !== 'th'));
|
|
27
|
+
return {
|
|
28
|
+
[`Element$1[name=${domElementsPattern}] > :matches(BoundAttribute, TextAttribute)[name='scope']`]({ sourceSpan, }) {
|
|
29
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
30
|
+
context.report({
|
|
31
|
+
loc,
|
|
32
|
+
messageId: 'tableScope',
|
|
33
|
+
fix: (fixer) => fixer.removeRange([
|
|
34
|
+
sourceSpan.start.offset - 1,
|
|
35
|
+
sourceSpan.end.offset,
|
|
36
|
+
]),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
6
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
7
|
+
exports.RULE_NAME = 'use-track-by-function';
|
|
8
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
|
+
name: exports.RULE_NAME,
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'Ensures trackBy function is used',
|
|
14
|
+
recommended: false,
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
useTrackByFunction: 'Missing trackBy function in ngFor directive',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
24
|
+
return {
|
|
25
|
+
'BoundAttribute.inputs[name="ngForOf"]'({ parent: { inputs }, sourceSpan, }) {
|
|
26
|
+
if (inputs.some(isNgForTrackBy)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
30
|
+
context.report({
|
|
31
|
+
messageId: 'useTrackByFunction',
|
|
32
|
+
loc,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
'BoundAttribute.templateAttrs[name="ngForOf"]'({ parent: { templateAttrs }, }) {
|
|
36
|
+
if (templateAttrs.some(isNgForTrackBy)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const { start } = parserServices.convertNodeSourceSpanToLoc(templateAttrs[0].sourceSpan);
|
|
40
|
+
const { end } = parserServices.convertNodeSourceSpanToLoc(templateAttrs[templateAttrs.length - 1].sourceSpan);
|
|
41
|
+
const loc = {
|
|
42
|
+
start: Object.assign(Object.assign({}, start), { column: start.column - 1 }),
|
|
43
|
+
end: Object.assign(Object.assign({}, end), { column: end.column + 1 }),
|
|
44
|
+
};
|
|
45
|
+
context.report({
|
|
46
|
+
messageId: 'useTrackByFunction',
|
|
47
|
+
loc,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
function isNgForTrackBy(attribute) {
|
|
54
|
+
return (attribute instanceof bundled_angular_compiler_1.TmplAstBoundAttribute &&
|
|
55
|
+
attribute.name === 'ngForTrackBy');
|
|
56
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
6
|
+
const aria_query_1 = require("aria-query");
|
|
7
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
8
|
+
const get_dom_elements_1 = require("../utils/get-dom-elements");
|
|
9
|
+
const to_pattern_1 = require("../utils/to-pattern");
|
|
10
|
+
exports.RULE_NAME = 'valid-aria';
|
|
11
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
12
|
+
name: exports.RULE_NAME,
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
docs: {
|
|
16
|
+
description: '[Accessibility] Ensures that correct ARIA attributes and respective values are used',
|
|
17
|
+
recommended: false,
|
|
18
|
+
},
|
|
19
|
+
hasSuggestions: true,
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
validAria: 'The `{{attribute}}` is an invalid ARIA attribute',
|
|
23
|
+
validAriaValue: 'The `{{attribute}}` has an invalid value. Check the valid values at https://raw.githack.com/w3c/aria/stable/#roles',
|
|
24
|
+
suggestRemoveInvalidAria: 'Remove attribute `{{attribute}}`',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultOptions: [],
|
|
28
|
+
create(context) {
|
|
29
|
+
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
30
|
+
const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
|
|
31
|
+
return {
|
|
32
|
+
[`Element$1[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name=/^aria-.+/]`](node) {
|
|
33
|
+
const { name: attribute, sourceSpan } = node;
|
|
34
|
+
const ariaPropertyDefinition = aria_query_1.aria.get(attribute);
|
|
35
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
36
|
+
if (!ariaPropertyDefinition) {
|
|
37
|
+
context.report({
|
|
38
|
+
loc,
|
|
39
|
+
messageId: 'validAria',
|
|
40
|
+
data: { attribute },
|
|
41
|
+
suggest: [
|
|
42
|
+
{
|
|
43
|
+
messageId: 'suggestRemoveInvalidAria',
|
|
44
|
+
data: { attribute },
|
|
45
|
+
fix: (fixer) => fixer.removeRange([
|
|
46
|
+
sourceSpan.start.offset - 1,
|
|
47
|
+
sourceSpan.end.offset,
|
|
48
|
+
]),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const ast = extractASTFrom(node);
|
|
55
|
+
if (canIgnoreNode(ast) ||
|
|
56
|
+
isValidAriaPropertyValue(ariaPropertyDefinition, ast.value)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
context.report({
|
|
60
|
+
loc,
|
|
61
|
+
messageId: 'validAriaValue',
|
|
62
|
+
data: { attribute },
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
function isLiteralCollection(ast) {
|
|
69
|
+
return ast instanceof bundled_angular_compiler_1.LiteralArray || ast instanceof bundled_angular_compiler_1.LiteralMap;
|
|
70
|
+
}
|
|
71
|
+
function isPrimitive(ast) {
|
|
72
|
+
return ast instanceof bundled_angular_compiler_1.LiteralPrimitive || ast instanceof bundled_angular_compiler_1.TmplAstTextAttribute;
|
|
73
|
+
}
|
|
74
|
+
function canIgnoreNode(ast) {
|
|
75
|
+
return !isLiteralCollection(ast) && !isPrimitive(ast);
|
|
76
|
+
}
|
|
77
|
+
function extractASTFrom(attribute) {
|
|
78
|
+
return attribute instanceof bundled_angular_compiler_1.TmplAstBoundAttribute &&
|
|
79
|
+
attribute.value instanceof bundled_angular_compiler_1.ASTWithSource
|
|
80
|
+
? attribute.value.ast
|
|
81
|
+
: attribute;
|
|
82
|
+
}
|
|
83
|
+
function isBooleanLike(value) {
|
|
84
|
+
return typeof value === 'boolean' || value === 'false' || value === 'true';
|
|
85
|
+
}
|
|
86
|
+
function isInteger(value) {
|
|
87
|
+
return (!Number.isNaN(value) &&
|
|
88
|
+
parseInt(Number(value)) == value &&
|
|
89
|
+
!Number.isNaN(parseInt(value, 10)));
|
|
90
|
+
}
|
|
91
|
+
function isNumeric(value) {
|
|
92
|
+
return (!Number.isNaN(Number.parseFloat(value)) &&
|
|
93
|
+
Number.isFinite(Number(value)));
|
|
94
|
+
}
|
|
95
|
+
function isNil(value) {
|
|
96
|
+
return value == null;
|
|
97
|
+
}
|
|
98
|
+
function isString(value) {
|
|
99
|
+
return typeof value == 'string';
|
|
100
|
+
}
|
|
101
|
+
function isValidAriaPropertyValue({ allowundefined, type, values }, attributeValue) {
|
|
102
|
+
if (allowundefined && isNil(attributeValue))
|
|
103
|
+
return true;
|
|
104
|
+
switch (type) {
|
|
105
|
+
case 'boolean':
|
|
106
|
+
return isBooleanLike(attributeValue);
|
|
107
|
+
case 'tristate':
|
|
108
|
+
return isBooleanLike(attributeValue) || isNil(attributeValue);
|
|
109
|
+
case 'id':
|
|
110
|
+
case 'idlist':
|
|
111
|
+
return true;
|
|
112
|
+
case 'integer':
|
|
113
|
+
return isInteger(attributeValue);
|
|
114
|
+
case 'number':
|
|
115
|
+
return isNumeric(attributeValue);
|
|
116
|
+
case 'string':
|
|
117
|
+
return isString(attributeValue);
|
|
118
|
+
case 'token':
|
|
119
|
+
case 'tokenlist': {
|
|
120
|
+
const parsedAttributeValue = isBooleanLike(attributeValue)
|
|
121
|
+
? JSON.parse(attributeValue)
|
|
122
|
+
: attributeValue;
|
|
123
|
+
return Boolean(values === null || values === void 0 ? void 0 : values.includes(parsedAttributeValue));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attributesComparator = void 0;
|
|
4
|
+
const get_attribute_value_1 = require("./get-attribute-value");
|
|
5
|
+
function attributesComparator(ariaRoleRelationConceptAttributes, node) {
|
|
6
|
+
const attributesInputs = [...node.attributes, ...node.inputs];
|
|
7
|
+
return ariaRoleRelationConceptAttributes.every((ariaRoleRelationConceptAttribute) => attributesInputs.some(({ name }) => {
|
|
8
|
+
if (node.name === 'a' && name === 'routerLink') {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
if (ariaRoleRelationConceptAttribute.name !== name) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return (!ariaRoleRelationConceptAttribute.value ||
|
|
15
|
+
ariaRoleRelationConceptAttribute.value ===
|
|
16
|
+
(0, get_attribute_value_1.getAttributeValue)(node, ariaRoleRelationConceptAttribute.name));
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
exports.attributesComparator = attributesComparator;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROPERTY_READ = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A representation of a property read.
|
|
6
|
+
* @example
|
|
7
|
+
* ```html
|
|
8
|
+
* <input [disabled]="disabled">
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
exports.PROPERTY_READ = Symbol('PROPERTY_READ');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createESLintRule = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
/**
|
|
6
|
+
* We need to patch the RuleCreator in order to preserve the defaultOptions
|
|
7
|
+
* to use as part of documentation generation.
|
|
8
|
+
*/
|
|
9
|
+
const patchedRuleCreator = (urlCreator) => {
|
|
10
|
+
return function createRule({ name, meta, defaultOptions, create }) {
|
|
11
|
+
return {
|
|
12
|
+
meta: Object.assign(Object.assign({}, meta), {
|
|
13
|
+
docs: Object.assign(Object.assign({}, meta.docs), {
|
|
14
|
+
url: urlCreator(name),
|
|
15
|
+
}),
|
|
16
|
+
}),
|
|
17
|
+
defaultOptions,
|
|
18
|
+
create(context) {
|
|
19
|
+
const optionsWithDefault = utils_1.ESLintUtils.applyDefault(defaultOptions, context.options);
|
|
20
|
+
return create(context, optionsWithDefault);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
patchedRuleCreator.withoutDocs = utils_1.ESLintUtils.RuleCreator.withoutDocs;
|
|
26
|
+
exports.createESLintRule = patchedRuleCreator((ruleName) => `https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/${ruleName}.md`);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAttributeValue = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const get_original_attribute_name_1 = require("./get-original-attribute-name");
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the attribute value.
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* getAttributeValue(Element(`<div property="test"></div>`), 'nonExistent'); // null
|
|
12
|
+
* getAttributeValue(Element(`<div aria-role="none"></div>`), 'role'); // 'none'
|
|
13
|
+
* getAttributeValue(Element(`<div [attr.aria-checked]="true"></div>`), 'aria-checked'); // true
|
|
14
|
+
* getAttributeValue(Element(`<button [variant]="variant"></button>`), 'variant'); // PROPERTY
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function getAttributeValue({ attributes, inputs }, attributeName) {
|
|
18
|
+
const attributeOrInput = [...attributes, ...inputs].find((attrOrInput) => (0, get_original_attribute_name_1.getOriginalAttributeName)(attrOrInput) === attributeName);
|
|
19
|
+
if (typeof (attributeOrInput === null || attributeOrInput === void 0 ? void 0 : attributeOrInput.value) === 'string') {
|
|
20
|
+
return attributeOrInput.value;
|
|
21
|
+
}
|
|
22
|
+
if (!((attributeOrInput === null || attributeOrInput === void 0 ? void 0 : attributeOrInput.value) instanceof bundled_angular_compiler_1.ASTWithSource)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (attributeOrInput.value.ast instanceof bundled_angular_compiler_1.LiteralArray) {
|
|
26
|
+
return attributeOrInput.value.ast.expressions;
|
|
27
|
+
}
|
|
28
|
+
if (attributeOrInput.value.ast instanceof bundled_angular_compiler_1.LiteralMap) {
|
|
29
|
+
const { keys, values } = attributeOrInput.value.ast;
|
|
30
|
+
return keys.reduce((current, next, index) => {
|
|
31
|
+
return current.set(next.key, values[index]);
|
|
32
|
+
}, new Map());
|
|
33
|
+
}
|
|
34
|
+
if (attributeOrInput.value.ast instanceof bundled_angular_compiler_1.LiteralPrimitive) {
|
|
35
|
+
return attributeOrInput.value.ast.value;
|
|
36
|
+
}
|
|
37
|
+
return constants_1.PROPERTY_READ;
|
|
38
|
+
}
|
|
39
|
+
exports.getAttributeValue = getAttributeValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDomElements = void 0;
|
|
4
|
+
const aria_query_1 = require("aria-query");
|
|
5
|
+
let domElements = null;
|
|
6
|
+
function getDomElements() {
|
|
7
|
+
return domElements !== null && domElements !== void 0 ? domElements : (domElements = new Set(aria_query_1.dom.keys()));
|
|
8
|
+
}
|
|
9
|
+
exports.getDomElements = getDomElements;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNearestNodeFrom = void 0;
|
|
4
|
+
const types_1 = require("@typescript-eslint/types");
|
|
5
|
+
function isProgram(node) {
|
|
6
|
+
return node.type === types_1.AST_NODE_TYPES.Program;
|
|
7
|
+
}
|
|
8
|
+
function getNearestNodeFrom({ parent }, predicate) {
|
|
9
|
+
while (parent && !isProgram(parent)) {
|
|
10
|
+
if (predicate(parent)) {
|
|
11
|
+
return parent;
|
|
12
|
+
}
|
|
13
|
+
parent = parent.parent;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
exports.getNearestNodeFrom = getNearestNodeFrom;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOriginalAttributeName = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
/**
|
|
6
|
+
* Returns the original attribute name.
|
|
7
|
+
* @example
|
|
8
|
+
* ```html
|
|
9
|
+
* <div [style.display.none]="test"></div> <!-- Instead of "display", "style.display.none" -->
|
|
10
|
+
* <div [attr.role]="'none'"></div> <!-- Instead of "attr.role", "role" -->
|
|
11
|
+
* <div ([ngModel])="test"></div> <!-- Instead of "ngModel", "ngModelChange" -->
|
|
12
|
+
* <div (@fade.start)="handle()"></div> <!-- Instead of "fade", "@fade.start" -->
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function getOriginalAttributeName(attribute) {
|
|
16
|
+
var _a;
|
|
17
|
+
const { details } = (_a = attribute.keySpan) !== null && _a !== void 0 ? _a : {};
|
|
18
|
+
if (!details) {
|
|
19
|
+
return attribute.name;
|
|
20
|
+
}
|
|
21
|
+
if (attribute instanceof bundled_angular_compiler_1.TmplAstBoundEvent) {
|
|
22
|
+
return isTwoWayDataBinding(attribute) ? attribute.name : details;
|
|
23
|
+
}
|
|
24
|
+
return details.replace('attr.', '');
|
|
25
|
+
}
|
|
26
|
+
exports.getOriginalAttributeName = getOriginalAttributeName;
|
|
27
|
+
function isTwoWayDataBinding({ keySpan: { details }, name, }) {
|
|
28
|
+
return name === `${details}Change`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isChildNodeOf = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
function isChildNodeOf(ast, childNodeName) {
|
|
6
|
+
function traverseChildNodes({ children }) {
|
|
7
|
+
return children.some((child) => child instanceof bundled_angular_compiler_1.TmplAstElement &&
|
|
8
|
+
(child.name === childNodeName || traverseChildNodes(child)));
|
|
9
|
+
}
|
|
10
|
+
return traverseChildNodes(ast);
|
|
11
|
+
}
|
|
12
|
+
exports.isChildNodeOf = isChildNodeOf;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isContentEditable = void 0;
|
|
4
|
+
const get_original_attribute_name_1 = require("./get-original-attribute-name");
|
|
5
|
+
const get_attribute_value_1 = require("./get-attribute-value");
|
|
6
|
+
function isContentEditable(node) {
|
|
7
|
+
const attributesInputs = [...node.attributes, ...node.inputs];
|
|
8
|
+
const contentEditableAttr = attributesInputs.find((attr) => (0, get_original_attribute_name_1.getOriginalAttributeName)(attr) === 'contenteditable');
|
|
9
|
+
const contentEditableValue = (0, get_attribute_value_1.getAttributeValue)(node, 'contenteditable');
|
|
10
|
+
return (!!contentEditableAttr &&
|
|
11
|
+
(contentEditableValue === '' ||
|
|
12
|
+
String(contentEditableValue).toLowerCase() === 'true'));
|
|
13
|
+
}
|
|
14
|
+
exports.isContentEditable = isContentEditable;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isDisabledElement = void 0;
|
|
4
|
+
const get_original_attribute_name_1 = require("./get-original-attribute-name");
|
|
5
|
+
const get_attribute_value_1 = require("./get-attribute-value");
|
|
6
|
+
function isDisabledElement(node) {
|
|
7
|
+
const attributesInputs = [...node.attributes, ...node.inputs];
|
|
8
|
+
const disabledAttr = attributesInputs.find((attr) => (0, get_original_attribute_name_1.getOriginalAttributeName)(attr) === 'disabled');
|
|
9
|
+
const disabledValue = (0, get_attribute_value_1.getAttributeValue)(node, 'disabled');
|
|
10
|
+
const isHTML5Disabled = disabledAttr && disabledValue !== undefined;
|
|
11
|
+
if (isHTML5Disabled) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
const isAriaDisabled = String((0, get_attribute_value_1.getAttributeValue)(node, 'aria-disabled')).toLowerCase() === 'true';
|
|
15
|
+
if (isAriaDisabled) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
exports.isDisabledElement = isDisabledElement;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isHiddenFromScreenReader = void 0;
|
|
4
|
+
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
|
+
const get_attribute_value_1 = require("./get-attribute-value");
|
|
6
|
+
const get_nearest_node_from_1 = require("./get-nearest-node-from");
|
|
7
|
+
/**
|
|
8
|
+
* Whether an element content cannot be read by a screen reader. It can happen in the following situations:
|
|
9
|
+
* - It has `display: none` or `visibility: hidden` style;
|
|
10
|
+
* - It has `aria-hidden` or `hidden` attribute;
|
|
11
|
+
* - It's an `<input type="hidden">`;
|
|
12
|
+
* - One of its ancestors met one of the following situations above.
|
|
13
|
+
*/
|
|
14
|
+
function isHiddenFromScreenReader(node) {
|
|
15
|
+
if ((0, get_nearest_node_from_1.getNearestNodeFrom)(node, isElementHiddenFromScreenReader) ||
|
|
16
|
+
hasHiddenStaticStyles(node) ||
|
|
17
|
+
hasHiddenStaticNgStyles(node) ||
|
|
18
|
+
hasHiddenDynamicStylesWithLiteralValues(node) ||
|
|
19
|
+
/**
|
|
20
|
+
* We can't know if the element is hidden from screen reader if the value of `aria-hidden` or `hidden`
|
|
21
|
+
* is set dynamically via an Angular property binding, so we just check for raw HTML truthiness here.
|
|
22
|
+
*/
|
|
23
|
+
isHtmlTruthy(node, 'aria-hidden') ||
|
|
24
|
+
isHtmlTruthy(node, 'hidden')) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (node.name.toUpperCase() !== 'INPUT') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const typeAttributeValue = (0, get_attribute_value_1.getAttributeValue)(node, 'type');
|
|
31
|
+
return (typeof typeAttributeValue === 'string' &&
|
|
32
|
+
typeAttributeValue.toUpperCase() === 'HIDDEN');
|
|
33
|
+
}
|
|
34
|
+
exports.isHiddenFromScreenReader = isHiddenFromScreenReader;
|
|
35
|
+
/**
|
|
36
|
+
* Whether an element has hidden static styles.
|
|
37
|
+
* @example
|
|
38
|
+
* ```html
|
|
39
|
+
* <div style="display: none;"></div>
|
|
40
|
+
* <div [style]="'visibility: hidden; color: red'"></div>
|
|
41
|
+
* <div ngStyle="{ 'display': 'none' }"></div>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
function hasHiddenStaticStyles(node) {
|
|
45
|
+
const styleAttributeValue = (0, get_attribute_value_1.getAttributeValue)(node, 'style');
|
|
46
|
+
if (typeof styleAttributeValue !== 'string') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const rawStyleAttributeValue = styleAttributeValue.replace(/[\s'"]/g, '');
|
|
50
|
+
return (rawStyleAttributeValue.includes('display:none') ||
|
|
51
|
+
rawStyleAttributeValue.includes('visibility:hidden'));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether an element has hidden dynamic styles with literal values.
|
|
55
|
+
* @example
|
|
56
|
+
* ```html
|
|
57
|
+
* <div [style.display]="'none'"></div>
|
|
58
|
+
* <div [visibility.hidden]="true"></div>
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
function hasHiddenDynamicStylesWithLiteralValues(node) {
|
|
62
|
+
return ((0, get_attribute_value_1.getAttributeValue)(node, 'style.display') === 'none' ||
|
|
63
|
+
String((0, get_attribute_value_1.getAttributeValue)(node, 'style.display.none')) === 'true' ||
|
|
64
|
+
(0, get_attribute_value_1.getAttributeValue)(node, 'style.visibility') === 'hidden' ||
|
|
65
|
+
String((0, get_attribute_value_1.getAttributeValue)(node, 'style.visibility.hidden')) === 'true');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether an element has `ngStyle` attribute containing `display: none` or `visibility: hidden`.
|
|
69
|
+
* @example
|
|
70
|
+
* ```html
|
|
71
|
+
* <div [ngStyle]="{ 'display': 'none' }"></div>
|
|
72
|
+
* <div [ngStyle]="{ 'visibility': 'hidden' }"></div>
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
function hasHiddenStaticNgStyles(node) {
|
|
76
|
+
const ngStyleAttributeValue = (0, get_attribute_value_1.getAttributeValue)(node, 'ngStyle');
|
|
77
|
+
return (ngStyleAttributeValue instanceof Map &&
|
|
78
|
+
(ngStyleAttributeValue.get('display') === 'none' ||
|
|
79
|
+
ngStyleAttributeValue.get('visibility') === 'hidden'));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether an attribute is equals to empty string, `'true'` or `true`.
|
|
83
|
+
* @example
|
|
84
|
+
* ```html
|
|
85
|
+
* <div hidden></div>
|
|
86
|
+
* <div [hidden]="true"></div>
|
|
87
|
+
* <div aria-hidden=""></div>
|
|
88
|
+
* <div aria-hidden="true"></div>
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
function isHtmlTruthy(node, attributeName) {
|
|
92
|
+
const attributeValue = (0, get_attribute_value_1.getAttributeValue)(node, attributeName);
|
|
93
|
+
return attributeValue === '' || String(attributeValue) === 'true';
|
|
94
|
+
}
|
|
95
|
+
function isElementHiddenFromScreenReader(node) {
|
|
96
|
+
return node instanceof bundled_angular_compiler_1.TmplAstElement && isHiddenFromScreenReader(node);
|
|
97
|
+
}
|