@elastic/eslint-plugin-eui 2.7.0 → 2.8.0
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/README.md +15 -0
- package/lib/cjs/index.js +8 -2
- package/lib/cjs/rules/a11y/badge_accessibility_rules.d.ts +3 -0
- package/lib/cjs/rules/a11y/badge_accessibility_rules.d.ts.map +1 -0
- package/lib/cjs/rules/a11y/badge_accessibility_rules.js +82 -0
- package/lib/cjs/rules/a11y/icon_accessibility_rules.d.ts +3 -0
- package/lib/cjs/rules/a11y/icon_accessibility_rules.d.ts.map +1 -0
- package/lib/cjs/rules/a11y/icon_accessibility_rules.js +82 -0
- package/lib/cjs/utils/remove_attr.d.ts +26 -0
- package/lib/cjs/utils/remove_attr.d.ts.map +1 -0
- package/lib/cjs/utils/remove_attr.js +39 -0
- package/lib/esm/index.js +6 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/rules/a11y/badge_accessibility_rules.d.ts +2 -0
- package/lib/esm/rules/a11y/badge_accessibility_rules.js +84 -0
- package/lib/esm/rules/a11y/badge_accessibility_rules.js.map +1 -0
- package/lib/esm/rules/a11y/icon_accessibility_rules.d.ts +2 -0
- package/lib/esm/rules/a11y/icon_accessibility_rules.js +87 -0
- package/lib/esm/rules/a11y/icon_accessibility_rules.js.map +1 -0
- package/lib/esm/utils/remove_attr.d.ts +25 -0
- package/lib/esm/utils/remove_attr.js +34 -0
- package/lib/esm/utils/remove_attr.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,6 +167,21 @@ Ensure interactive EUI components (like e.g. `EuiLink`, `EuiButton`, `EuiRadio`)
|
|
|
167
167
|
|
|
168
168
|
Ensure `EuiInMemoryTable`, `EuiBasicTable` have a `tableCaption` property for accessibility.
|
|
169
169
|
|
|
170
|
+
### `@elastic/eui/badge-accessibility-rules`
|
|
171
|
+
|
|
172
|
+
Ensure the `EuiBadge` includes appropriate accessibility attributes.
|
|
173
|
+
|
|
174
|
+
- `iconOnClick` and `onClick` must not reference the same callback. The rule autofixes by removing `iconOnClick`.
|
|
175
|
+
- `iconOnClickAriaLabel` is only valid when `iconOnClick` is present. The rule autofixes by removing `iconOnClickAriaLabel`.
|
|
176
|
+
- `onClickAriaLabel` is only valid when `onClick` is present. The rule autofixes by removing `onClickAriaLabel`.
|
|
177
|
+
|
|
178
|
+
### `@elastic/eui/icon-accessibility-rules`
|
|
179
|
+
|
|
180
|
+
Ensure the `EuiIcon` includes appropriate accessibility attributes.
|
|
181
|
+
|
|
182
|
+
- `EuiIcon` has an accessible name via `title`, `aria-label`, or `aria-labelledby`; otherwise mark it decorative with `aria-hidden={true}`
|
|
183
|
+
- Do not combine `tabIndex` with `aria-hidden`
|
|
184
|
+
|
|
170
185
|
## Testing
|
|
171
186
|
|
|
172
187
|
### Running unit tests
|
package/lib/cjs/index.js
CHANGED
|
@@ -14,6 +14,8 @@ var _require_aria_label_for_modals = require("./rules/a11y/require_aria_label_fo
|
|
|
14
14
|
var _require_table_caption = require("./rules/a11y/require_table_caption");
|
|
15
15
|
var _sr_output_disabled_tooltip = require("./rules/a11y/sr_output_disabled_tooltip");
|
|
16
16
|
var _tooltip_focusable_anchor = require("./rules/a11y/tooltip_focusable_anchor");
|
|
17
|
+
var _badge_accessibility_rules = require("./rules/a11y/badge_accessibility_rules");
|
|
18
|
+
var _icon_accessibility_rules = require("./rules/a11y/icon_accessibility_rules");
|
|
17
19
|
/*
|
|
18
20
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
19
21
|
* or more contributor license agreements. Licensed under the Elastic License
|
|
@@ -37,7 +39,9 @@ const config = {
|
|
|
37
39
|
'require-aria-label-for-modals': _require_aria_label_for_modals.RequireAriaLabelForModals,
|
|
38
40
|
'require-table-caption': _require_table_caption.RequireTableCaption,
|
|
39
41
|
'sr-output-disabled-tooltip': _sr_output_disabled_tooltip.ScreenReaderOutputDisabledTooltip,
|
|
40
|
-
'tooltip-focusable-anchor': _tooltip_focusable_anchor.TooltipFocusableAnchor
|
|
42
|
+
'tooltip-focusable-anchor': _tooltip_focusable_anchor.TooltipFocusableAnchor,
|
|
43
|
+
'badge-accessibility-rules': _badge_accessibility_rules.EuiBadgeAccessibilityRules,
|
|
44
|
+
'icon-accessibility-rules': _icon_accessibility_rules.EuiIconAccessibilityRules
|
|
41
45
|
},
|
|
42
46
|
configs: {
|
|
43
47
|
recommended: {
|
|
@@ -56,7 +60,9 @@ const config = {
|
|
|
56
60
|
'@elastic/eui/require-aria-label-for-modals': 'warn',
|
|
57
61
|
'@elastic/eui/require-table-caption': 'warn',
|
|
58
62
|
'@elastic/eui/sr-output-disabled-tooltip': 'warn',
|
|
59
|
-
'@elastic/eui/tooltip-focusable-anchor': 'warn'
|
|
63
|
+
'@elastic/eui/tooltip-focusable-anchor': 'warn',
|
|
64
|
+
'@elastic/eui/badge-accessibility-rules': 'warn',
|
|
65
|
+
'@elastic/eui/icon-accessibility-rules': 'warn'
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const EuiBadgeAccessibilityRules: ESLintUtils.RuleModule<"sameCallback" | "iconOnClickAriaLabelWithoutIconOnClick" | "onClickAriaLabelWithoutOnClick", [], unknown, ESLintUtils.RuleListener>;
|
|
3
|
+
//# sourceMappingURL=badge_accessibility_rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge_accessibility_rules.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/badge_accessibility_rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD,eAAO,MAAM,0BAA0B,6JAyFrC,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.EuiBadgeAccessibilityRules = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
const BADGE_COMPONENT = 'EuiBadge';
|
|
9
|
+
const EuiBadgeAccessibilityRules = exports.EuiBadgeAccessibilityRules = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
10
|
+
create(context) {
|
|
11
|
+
return {
|
|
12
|
+
JSXElement(node) {
|
|
13
|
+
const {
|
|
14
|
+
openingElement
|
|
15
|
+
} = node;
|
|
16
|
+
if (openingElement.name.type !== 'JSXIdentifier' || openingElement.name.name !== BADGE_COMPONENT) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let iconOnClick, onClick, iconOnClickAriaLabel, onClickAriaLabel;
|
|
20
|
+
for (const attr of openingElement.attributes) {
|
|
21
|
+
if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
|
|
22
|
+
switch (attr.name.name) {
|
|
23
|
+
case 'iconOnClick':
|
|
24
|
+
iconOnClick = attr;
|
|
25
|
+
break;
|
|
26
|
+
case 'onClick':
|
|
27
|
+
onClick = attr;
|
|
28
|
+
break;
|
|
29
|
+
case 'iconOnClickAriaLabel':
|
|
30
|
+
iconOnClickAriaLabel = attr;
|
|
31
|
+
break;
|
|
32
|
+
case 'onClickAriaLabel':
|
|
33
|
+
onClickAriaLabel = attr;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 1. iconOnClick and onClick cannot refer to the same callback
|
|
40
|
+
if (iconOnClick && onClick && iconOnClick.value && onClick.value && iconOnClick.value.type === 'JSXExpressionContainer' && onClick.value.type === 'JSXExpressionContainer' && iconOnClick.value.expression.type === 'Identifier' && onClick.value.expression.type === 'Identifier' && iconOnClick.value.expression.name === onClick.value.expression.name) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: iconOnClick,
|
|
43
|
+
messageId: 'sameCallback',
|
|
44
|
+
fix: fixer => fixer.removeRange([iconOnClick.range[0], iconOnClick.range[1]])
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. iconOnClickAriaLabel should be set only if iconOnClick is set
|
|
49
|
+
if (iconOnClickAriaLabel && !iconOnClick) {
|
|
50
|
+
context.report({
|
|
51
|
+
node: iconOnClickAriaLabel,
|
|
52
|
+
messageId: 'iconOnClickAriaLabelWithoutIconOnClick',
|
|
53
|
+
fix: fixer => fixer.removeRange([iconOnClickAriaLabel.range[0], iconOnClickAriaLabel.range[1]])
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. onClickAriaLabel should be set only if onClick is set
|
|
58
|
+
if (onClickAriaLabel && !onClick) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: onClickAriaLabel,
|
|
61
|
+
messageId: 'onClickAriaLabelWithoutOnClick',
|
|
62
|
+
fix: fixer => fixer.removeRange([onClickAriaLabel.range[0], onClickAriaLabel.range[1]])
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
meta: {
|
|
69
|
+
type: 'problem',
|
|
70
|
+
docs: {
|
|
71
|
+
description: `Ensure the ${BADGE_COMPONENT} includes appropriate accessibility attributes`
|
|
72
|
+
},
|
|
73
|
+
fixable: 'code',
|
|
74
|
+
schema: [],
|
|
75
|
+
messages: {
|
|
76
|
+
sameCallback: '`iconOnClick` and `onClick` should not refer to the same callback. Remove `iconOnClick` and keep only `onClick`.',
|
|
77
|
+
iconOnClickAriaLabelWithoutIconOnClick: '`iconOnClickAriaLabel` should only be set if `iconOnClick` is present. Remove this prop.',
|
|
78
|
+
onClickAriaLabelWithoutOnClick: '`onClickAriaLabel` should only be set if `onClick` is present. Remove this prop.'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
defaultOptions: []
|
|
82
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const EuiIconAccessibilityRules: ESLintUtils.RuleModule<"missingTitleOrAriaHidden" | "tabIndexWithAriaHidden", [], unknown, ESLintUtils.RuleListener>;
|
|
3
|
+
//# sourceMappingURL=icon_accessibility_rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon_accessibility_rules.d.ts","sourceRoot":"","sources":["../../../../src/rules/a11y/icon_accessibility_rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,0BAA0B,CAAC;AAKjE,eAAO,MAAM,yBAAyB,sHAyFpC,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.EuiIconAccessibilityRules = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var _remove_attr = require("../../utils/remove_attr");
|
|
9
|
+
const COMPONENT = 'EuiIcon';
|
|
10
|
+
const EuiIconAccessibilityRules = exports.EuiIconAccessibilityRules = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
JSXElement(node) {
|
|
14
|
+
const {
|
|
15
|
+
openingElement
|
|
16
|
+
} = node;
|
|
17
|
+
if (openingElement.name.type !== 'JSXIdentifier' || openingElement.name.name !== COMPONENT) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let ariaHiddenAttr;
|
|
21
|
+
let tabIndexAttr;
|
|
22
|
+
let isIconNamed = false;
|
|
23
|
+
for (const attr of openingElement.attributes) {
|
|
24
|
+
if (attr.type !== 'JSXAttribute' || attr.name.type !== 'JSXIdentifier') continue;
|
|
25
|
+
const name = attr.name.name;
|
|
26
|
+
if (name === 'aria-hidden') ariaHiddenAttr = attr;
|
|
27
|
+
if (name === 'tabIndex') tabIndexAttr = attr;
|
|
28
|
+
if (['title', 'aria-labelledby', 'aria-label'].includes(name)) {
|
|
29
|
+
isIconNamed = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const hasAriaHiddenTrue = !!ariaHiddenAttr && ariaHiddenAttr.value && (
|
|
33
|
+
// aria-hidden={true}
|
|
34
|
+
ariaHiddenAttr.value.type === 'JSXExpressionContainer' && ariaHiddenAttr.value.expression.type === 'Literal' && ariaHiddenAttr.value.expression.value === true ||
|
|
35
|
+
// aria-hidden='true'
|
|
36
|
+
ariaHiddenAttr.value.type === 'Literal' && ariaHiddenAttr.value.value === 'true');
|
|
37
|
+
|
|
38
|
+
// Case: `tabIndex` and `aria-hidden` cannot be used together
|
|
39
|
+
if (tabIndexAttr && hasAriaHiddenTrue) {
|
|
40
|
+
context.report({
|
|
41
|
+
node: openingElement,
|
|
42
|
+
messageId: 'tabIndexWithAriaHidden',
|
|
43
|
+
fix: fixer => {
|
|
44
|
+
if (!ariaHiddenAttr?.range) return null;
|
|
45
|
+
const [start, end] = (0, _remove_attr.removeAttribute)(context, ariaHiddenAttr);
|
|
46
|
+
return [fixer.removeRange([start, end])];
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Require accessible name or `aria-hidden={true}`;
|
|
53
|
+
if (!isIconNamed && !hasAriaHiddenTrue) {
|
|
54
|
+
context.report({
|
|
55
|
+
node: openingElement,
|
|
56
|
+
messageId: 'missingTitleOrAriaHidden',
|
|
57
|
+
fix: fixer => {
|
|
58
|
+
if (tabIndexAttr) return null;
|
|
59
|
+
const end = openingElement.range[1];
|
|
60
|
+
const insertPos = openingElement.selfClosing ? end - 2 : end - 1; // before '/>' or '>'
|
|
61
|
+
const insertRange = [insertPos, insertPos];
|
|
62
|
+
return [fixer.insertTextBeforeRange(insertRange, ' aria-hidden={true}')];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
meta: {
|
|
70
|
+
type: 'suggestion',
|
|
71
|
+
docs: {
|
|
72
|
+
description: `Ensure the EuiIcon includes appropriate accessibility attributes`
|
|
73
|
+
},
|
|
74
|
+
fixable: 'code',
|
|
75
|
+
schema: [],
|
|
76
|
+
messages: {
|
|
77
|
+
missingTitleOrAriaHidden: 'Add a `title`, `aria-label`, or `aria-labelledby` to EuiIcon, or set `aria-hidden={true}` if it is decorative.',
|
|
78
|
+
tabIndexWithAriaHidden: 'Do not use `tabIndex` together with `aria-hidden`. Remove `aria-hidden` or provide an accessible name.'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
defaultOptions: []
|
|
82
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type TSESTree, type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the removal range for a JSX attribute, including a preceding space
|
|
4
|
+
* when present, to keep formatting intact after autofix.
|
|
5
|
+
*
|
|
6
|
+
* This helper is useful in ESLint rule fixers when calling `fixer.removeRange(...)`,
|
|
7
|
+
* ensuring that the attribute and its leading space are removed cleanly.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam TContext - An ESLint rule context type extending `TSESLint.RuleContext`.
|
|
10
|
+
* @param context - The current ESLint rule context providing access to `SourceCode`.
|
|
11
|
+
* @param attr - The JSX attribute node to remove.
|
|
12
|
+
* @returns A readonly tuple `[start, end]` representing the inclusive start and exclusive end indexes for removal.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* context.report({
|
|
17
|
+
* node: openingElement,
|
|
18
|
+
* messageId: 'removeAttr',
|
|
19
|
+
* fix: fixer => {
|
|
20
|
+
* const [start, end] = removeAttribute(context, ariaHiddenAttr);
|
|
21
|
+
* return fixer.removeRange([start, end]);
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
**/
|
|
25
|
+
export declare function removeAttribute<TContext extends TSESLint.RuleContext<string, unknown[]>>(context: TContext, attr: TSESTree.JSXAttribute): readonly [number, number];
|
|
26
|
+
//# sourceMappingURL=remove_attr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove_attr.d.ts","sourceRoot":"","sources":["../../../src/utils/remove_attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;IAsBI;AAEJ,wBAAgB,eAAe,CAC7B,QAAQ,SAAS,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAExD,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,QAAQ,CAAC,YAAY,6BAO5B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.removeAttribute = removeAttribute;
|
|
7
|
+
/**
|
|
8
|
+
* Computes the removal range for a JSX attribute, including a preceding space
|
|
9
|
+
* when present, to keep formatting intact after autofix.
|
|
10
|
+
*
|
|
11
|
+
* This helper is useful in ESLint rule fixers when calling `fixer.removeRange(...)`,
|
|
12
|
+
* ensuring that the attribute and its leading space are removed cleanly.
|
|
13
|
+
*
|
|
14
|
+
* @typeParam TContext - An ESLint rule context type extending `TSESLint.RuleContext`.
|
|
15
|
+
* @param context - The current ESLint rule context providing access to `SourceCode`.
|
|
16
|
+
* @param attr - The JSX attribute node to remove.
|
|
17
|
+
* @returns A readonly tuple `[start, end]` representing the inclusive start and exclusive end indexes for removal.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* context.report({
|
|
22
|
+
* node: openingElement,
|
|
23
|
+
* messageId: 'removeAttr',
|
|
24
|
+
* fix: fixer => {
|
|
25
|
+
* const [start, end] = removeAttribute(context, ariaHiddenAttr);
|
|
26
|
+
* return fixer.removeRange([start, end]);
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
**/
|
|
30
|
+
|
|
31
|
+
function removeAttribute(context, attr) {
|
|
32
|
+
const {
|
|
33
|
+
sourceCode
|
|
34
|
+
} = context;
|
|
35
|
+
const start = attr.range[0];
|
|
36
|
+
const before = sourceCode.text[start - 1];
|
|
37
|
+
const rangeStart = before === ' ' ? start - 1 : start;
|
|
38
|
+
return [rangeStart, attr.range[1]];
|
|
39
|
+
}
|
package/lib/esm/index.js
CHANGED
|
@@ -21,6 +21,8 @@ const require_aria_label_for_modals_1 = require("./rules/a11y/require_aria_label
|
|
|
21
21
|
const require_table_caption_1 = require("./rules/a11y/require_table_caption");
|
|
22
22
|
const sr_output_disabled_tooltip_1 = require("./rules/a11y/sr_output_disabled_tooltip");
|
|
23
23
|
const tooltip_focusable_anchor_1 = require("./rules/a11y/tooltip_focusable_anchor");
|
|
24
|
+
const badge_accessibility_rules_1 = require("./rules/a11y/badge_accessibility_rules");
|
|
25
|
+
const icon_accessibility_rules_1 = require("./rules/a11y/icon_accessibility_rules");
|
|
24
26
|
const config = {
|
|
25
27
|
rules: {
|
|
26
28
|
'accessible-interactive-element': accessible_interactive_element_1.AccessibleInteractiveElements,
|
|
@@ -37,6 +39,8 @@ const config = {
|
|
|
37
39
|
'require-table-caption': require_table_caption_1.RequireTableCaption,
|
|
38
40
|
'sr-output-disabled-tooltip': sr_output_disabled_tooltip_1.ScreenReaderOutputDisabledTooltip,
|
|
39
41
|
'tooltip-focusable-anchor': tooltip_focusable_anchor_1.TooltipFocusableAnchor,
|
|
42
|
+
'badge-accessibility-rules': badge_accessibility_rules_1.EuiBadgeAccessibilityRules,
|
|
43
|
+
'icon-accessibility-rules': icon_accessibility_rules_1.EuiIconAccessibilityRules
|
|
40
44
|
},
|
|
41
45
|
configs: {
|
|
42
46
|
recommended: {
|
|
@@ -56,6 +60,8 @@ const config = {
|
|
|
56
60
|
'@elastic/eui/require-table-caption': 'warn',
|
|
57
61
|
'@elastic/eui/sr-output-disabled-tooltip': 'warn',
|
|
58
62
|
'@elastic/eui/tooltip-focusable-anchor': 'warn',
|
|
63
|
+
'@elastic/eui/badge-accessibility-rules': 'warn',
|
|
64
|
+
'@elastic/eui/icon-accessibility-rules': 'warn',
|
|
59
65
|
},
|
|
60
66
|
},
|
|
61
67
|
},
|
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAGH,gGAA4F;AAC5F,sFAAgF;AAChF,0FAAoF;AACpF,+DAAuD;AACvD,uDAAkD;AAClD,iFAA2E;AAC3E,iEAA2D;AAC3D,gGAA0F;AAC1F,gFAA0E;AAC1E,0EAAoE;AACpE,8FAAuF;AACvF,8EAAyE;AACzE,wFAA4F;AAC5F,oFAA+E;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAGH,gGAA4F;AAC5F,sFAAgF;AAChF,0FAAoF;AACpF,+DAAuD;AACvD,uDAAkD;AAClD,iFAA2E;AAC3E,iEAA2D;AAC3D,gGAA0F;AAC1F,gFAA0E;AAC1E,0EAAoE;AACpE,8FAAuF;AACvF,8EAAyE;AACzE,wFAA4F;AAC5F,oFAA+E;AAC/E,sFAAoF;AACpF,oFAAkF;AAElF,MAAM,MAAM,GAAG;IACb,KAAK,EAAE;QACL,gCAAgC,EAAE,8DAA6B;QAC/D,2BAA2B,EAAE,kDAAsB;QACnD,6BAA6B,EAAE,sDAAwB;QACvD,kBAAkB,EAAE,8BAAW;QAC/B,cAAc,EAAE,yBAAU;QAC1B,2BAA2B,EAAE,kDAAsB;QACnD,mBAAmB,EAAE,kCAAc;QACnC,gCAAgC,EAAE,4DAA2B;QAC7D,wBAAwB,EAAG,4CAAmB;QAC9C,qBAAqB,EAAE,sCAAgB;QACvC,+BAA+B,EAAE,yDAAyB;QAC1D,uBAAuB,EAAE,2CAAmB;QAC5C,4BAA4B,EAAE,8DAAiC;QAC/D,0BAA0B,EAAE,iDAAsB;QAClD,2BAA2B,EAAE,sDAA0B;QACvD,0BAA0B,EAAE,oDAAyB;KACtD;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,4BAA4B,CAAC;YACvC,KAAK,EAAE;gBACL,6CAA6C,EAAE,MAAM;gBACrD,wCAAwC,EAAE,MAAM;gBAChD,0CAA0C,EAAE,MAAM;gBAClD,+BAA+B,EAAE,MAAM;gBACvC,2BAA2B,EAAE,MAAM;gBACnC,wCAAwC,EAAE,MAAM;gBAChD,gCAAgC,EAAE,MAAM;gBACxC,6CAA6C,EAAE,MAAM;gBACrD,qCAAqC,EAAE,MAAM;gBAC7C,kCAAkC,EAAE,MAAM;gBAC1C,4CAA4C,EAAE,MAAM;gBACpD,oCAAoC,EAAE,MAAM;gBAC5C,yCAAyC,EAAE,MAAM;gBACjD,uCAAuC,EAAE,MAAM;gBAC/C,wCAAwC,EAAE,MAAM;gBAChD,uCAAuC,EAAE,MAAM;aAChD;SACF;KACF;CACF,CAAC;AAEF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EuiBadgeAccessibilityRules = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const BADGE_COMPONENT = 'EuiBadge';
|
|
6
|
+
exports.EuiBadgeAccessibilityRules = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
7
|
+
create(context) {
|
|
8
|
+
return {
|
|
9
|
+
JSXElement(node) {
|
|
10
|
+
const { openingElement } = node;
|
|
11
|
+
if (openingElement.name.type !== 'JSXIdentifier' ||
|
|
12
|
+
openingElement.name.name !== BADGE_COMPONENT) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let iconOnClick, onClick, iconOnClickAriaLabel, onClickAriaLabel;
|
|
16
|
+
for (const attr of openingElement.attributes) {
|
|
17
|
+
if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
|
|
18
|
+
switch (attr.name.name) {
|
|
19
|
+
case 'iconOnClick':
|
|
20
|
+
iconOnClick = attr;
|
|
21
|
+
break;
|
|
22
|
+
case 'onClick':
|
|
23
|
+
onClick = attr;
|
|
24
|
+
break;
|
|
25
|
+
case 'iconOnClickAriaLabel':
|
|
26
|
+
iconOnClickAriaLabel = attr;
|
|
27
|
+
break;
|
|
28
|
+
case 'onClickAriaLabel':
|
|
29
|
+
onClickAriaLabel = attr;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// 1. iconOnClick and onClick cannot refer to the same callback
|
|
35
|
+
if (iconOnClick &&
|
|
36
|
+
onClick &&
|
|
37
|
+
iconOnClick.value &&
|
|
38
|
+
onClick.value &&
|
|
39
|
+
iconOnClick.value.type === 'JSXExpressionContainer' &&
|
|
40
|
+
onClick.value.type === 'JSXExpressionContainer' &&
|
|
41
|
+
iconOnClick.value.expression.type === 'Identifier' &&
|
|
42
|
+
onClick.value.expression.type === 'Identifier' &&
|
|
43
|
+
iconOnClick.value.expression.name === onClick.value.expression.name) {
|
|
44
|
+
context.report({
|
|
45
|
+
node: iconOnClick,
|
|
46
|
+
messageId: 'sameCallback',
|
|
47
|
+
fix: fixer => fixer.removeRange([iconOnClick.range[0], iconOnClick.range[1]]),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// 2. iconOnClickAriaLabel should be set only if iconOnClick is set
|
|
51
|
+
if (iconOnClickAriaLabel && !iconOnClick) {
|
|
52
|
+
context.report({
|
|
53
|
+
node: iconOnClickAriaLabel,
|
|
54
|
+
messageId: 'iconOnClickAriaLabelWithoutIconOnClick',
|
|
55
|
+
fix: fixer => fixer.removeRange([iconOnClickAriaLabel.range[0], iconOnClickAriaLabel.range[1]]),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// 3. onClickAriaLabel should be set only if onClick is set
|
|
59
|
+
if (onClickAriaLabel && !onClick) {
|
|
60
|
+
context.report({
|
|
61
|
+
node: onClickAriaLabel,
|
|
62
|
+
messageId: 'onClickAriaLabelWithoutOnClick',
|
|
63
|
+
fix: fixer => fixer.removeRange([onClickAriaLabel.range[0], onClickAriaLabel.range[1]]),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
meta: {
|
|
70
|
+
type: 'problem',
|
|
71
|
+
docs: {
|
|
72
|
+
description: `Ensure the ${BADGE_COMPONENT} includes appropriate accessibility attributes`,
|
|
73
|
+
},
|
|
74
|
+
fixable: 'code',
|
|
75
|
+
schema: [],
|
|
76
|
+
messages: {
|
|
77
|
+
sameCallback: '`iconOnClick` and `onClick` should not refer to the same callback. Remove `iconOnClick` and keep only `onClick`.',
|
|
78
|
+
iconOnClickAriaLabelWithoutIconOnClick: '`iconOnClickAriaLabel` should only be set if `iconOnClick` is present. Remove this prop.',
|
|
79
|
+
onClickAriaLabelWithoutOnClick: '`onClickAriaLabel` should only be set if `onClick` is present. Remove this prop.',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
defaultOptions: [],
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=badge_accessibility_rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge_accessibility_rules.js","sourceRoot":"","sources":["../../../../src/rules/a11y/badge_accessibility_rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EuiIconAccessibilityRules = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const remove_attr_1 = require("../../utils/remove_attr");
|
|
6
|
+
const COMPONENT = 'EuiIcon';
|
|
7
|
+
exports.EuiIconAccessibilityRules = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
8
|
+
create(context) {
|
|
9
|
+
return {
|
|
10
|
+
JSXElement(node) {
|
|
11
|
+
const { openingElement } = node;
|
|
12
|
+
if (openingElement.name.type !== 'JSXIdentifier' ||
|
|
13
|
+
openingElement.name.name !== COMPONENT) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let ariaHiddenAttr;
|
|
17
|
+
let tabIndexAttr;
|
|
18
|
+
let isIconNamed = false;
|
|
19
|
+
for (const attr of openingElement.attributes) {
|
|
20
|
+
if (attr.type !== 'JSXAttribute' || attr.name.type !== 'JSXIdentifier')
|
|
21
|
+
continue;
|
|
22
|
+
const name = attr.name.name;
|
|
23
|
+
if (name === 'aria-hidden')
|
|
24
|
+
ariaHiddenAttr = attr;
|
|
25
|
+
if (name === 'tabIndex')
|
|
26
|
+
tabIndexAttr = attr;
|
|
27
|
+
if (['title', 'aria-labelledby', 'aria-label'].includes(name)) {
|
|
28
|
+
isIconNamed = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const hasAriaHiddenTrue = !!ariaHiddenAttr &&
|
|
32
|
+
ariaHiddenAttr.value &&
|
|
33
|
+
(
|
|
34
|
+
// aria-hidden={true}
|
|
35
|
+
(ariaHiddenAttr.value.type === 'JSXExpressionContainer' &&
|
|
36
|
+
ariaHiddenAttr.value.expression.type === 'Literal' &&
|
|
37
|
+
ariaHiddenAttr.value.expression.value === true) ||
|
|
38
|
+
// aria-hidden='true'
|
|
39
|
+
(ariaHiddenAttr.value.type === 'Literal' &&
|
|
40
|
+
ariaHiddenAttr.value.value === 'true'));
|
|
41
|
+
// Case: `tabIndex` and `aria-hidden` cannot be used together
|
|
42
|
+
if (tabIndexAttr && hasAriaHiddenTrue) {
|
|
43
|
+
context.report({
|
|
44
|
+
node: openingElement,
|
|
45
|
+
messageId: 'tabIndexWithAriaHidden',
|
|
46
|
+
fix: fixer => {
|
|
47
|
+
if (!ariaHiddenAttr?.range)
|
|
48
|
+
return null;
|
|
49
|
+
const [start, end] = (0, remove_attr_1.removeAttribute)(context, ariaHiddenAttr);
|
|
50
|
+
return [fixer.removeRange([start, end])];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Require accessible name or `aria-hidden={true}`;
|
|
56
|
+
if (!isIconNamed && !hasAriaHiddenTrue) {
|
|
57
|
+
context.report({
|
|
58
|
+
node: openingElement,
|
|
59
|
+
messageId: 'missingTitleOrAriaHidden',
|
|
60
|
+
fix: fixer => {
|
|
61
|
+
if (tabIndexAttr)
|
|
62
|
+
return null;
|
|
63
|
+
const end = openingElement.range[1];
|
|
64
|
+
const insertPos = openingElement.selfClosing ? end - 2 : end - 1; // before '/>' or '>'
|
|
65
|
+
const insertRange = [insertPos, insertPos];
|
|
66
|
+
return [fixer.insertTextBeforeRange(insertRange, ' aria-hidden={true}')];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
meta: {
|
|
74
|
+
type: 'suggestion',
|
|
75
|
+
docs: {
|
|
76
|
+
description: `Ensure the EuiIcon includes appropriate accessibility attributes`
|
|
77
|
+
},
|
|
78
|
+
fixable: 'code',
|
|
79
|
+
schema: [],
|
|
80
|
+
messages: {
|
|
81
|
+
missingTitleOrAriaHidden: 'Add a `title`, `aria-label`, or `aria-labelledby` to EuiIcon, or set `aria-hidden={true}` if it is decorative.',
|
|
82
|
+
tabIndexWithAriaHidden: 'Do not use `tabIndex` together with `aria-hidden`. Remove `aria-hidden` or provide an accessible name.'
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
defaultOptions: []
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=icon_accessibility_rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon_accessibility_rules.js","sourceRoot":"","sources":["../../../../src/rules/a11y/icon_accessibility_rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type TSESTree, type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the removal range for a JSX attribute, including a preceding space
|
|
4
|
+
* when present, to keep formatting intact after autofix.
|
|
5
|
+
*
|
|
6
|
+
* This helper is useful in ESLint rule fixers when calling `fixer.removeRange(...)`,
|
|
7
|
+
* ensuring that the attribute and its leading space are removed cleanly.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam TContext - An ESLint rule context type extending `TSESLint.RuleContext`.
|
|
10
|
+
* @param context - The current ESLint rule context providing access to `SourceCode`.
|
|
11
|
+
* @param attr - The JSX attribute node to remove.
|
|
12
|
+
* @returns A readonly tuple `[start, end]` representing the inclusive start and exclusive end indexes for removal.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* context.report({
|
|
17
|
+
* node: openingElement,
|
|
18
|
+
* messageId: 'removeAttr',
|
|
19
|
+
* fix: fixer => {
|
|
20
|
+
* const [start, end] = removeAttribute(context, ariaHiddenAttr);
|
|
21
|
+
* return fixer.removeRange([start, end]);
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
**/
|
|
25
|
+
export declare function removeAttribute<TContext extends TSESLint.RuleContext<string, unknown[]>>(context: TContext, attr: TSESTree.JSXAttribute): readonly [number, number];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeAttribute = removeAttribute;
|
|
4
|
+
/**
|
|
5
|
+
* Computes the removal range for a JSX attribute, including a preceding space
|
|
6
|
+
* when present, to keep formatting intact after autofix.
|
|
7
|
+
*
|
|
8
|
+
* This helper is useful in ESLint rule fixers when calling `fixer.removeRange(...)`,
|
|
9
|
+
* ensuring that the attribute and its leading space are removed cleanly.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam TContext - An ESLint rule context type extending `TSESLint.RuleContext`.
|
|
12
|
+
* @param context - The current ESLint rule context providing access to `SourceCode`.
|
|
13
|
+
* @param attr - The JSX attribute node to remove.
|
|
14
|
+
* @returns A readonly tuple `[start, end]` representing the inclusive start and exclusive end indexes for removal.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* context.report({
|
|
19
|
+
* node: openingElement,
|
|
20
|
+
* messageId: 'removeAttr',
|
|
21
|
+
* fix: fixer => {
|
|
22
|
+
* const [start, end] = removeAttribute(context, ariaHiddenAttr);
|
|
23
|
+
* return fixer.removeRange([start, end]);
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
**/
|
|
27
|
+
function removeAttribute(context, attr) {
|
|
28
|
+
const { sourceCode } = context;
|
|
29
|
+
const start = attr.range[0];
|
|
30
|
+
const before = sourceCode.text[start - 1];
|
|
31
|
+
const rangeStart = before === ' ' ? start - 1 : start;
|
|
32
|
+
return [rangeStart, attr.range[1]];
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=remove_attr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove_attr.js","sourceRoot":"","sources":["../../../src/utils/remove_attr.ts"],"names":[],"mappings":";;AA0BA,0CAWC;AAnCD;;;;;;;;;;;;;;;;;;;;;;IAsBI;AAEJ,SAAgB,eAAe,CAG7B,OAAiB,EACjB,IAA2B;IAC3B,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEtD,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAU,CAAC;AAC9C,CAAC"}
|