@hypersolid-dev/eslint-config 2.0.0 → 4.0.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/CHANGELOG.md +21 -0
- package/README.md +12 -1
- package/dist/plugins/react-native-a11y/constants/accessibility-props.d.ts +32 -0
- package/dist/plugins/react-native-a11y/constants/accessibility-props.js +50 -0
- package/dist/plugins/react-native-a11y/constants/accessibility-state.d.ts +2 -0
- package/dist/plugins/react-native-a11y/constants/accessibility-state.js +8 -0
- package/dist/plugins/react-native-a11y/constants/interactive.d.ts +6 -0
- package/dist/plugins/react-native-a11y/constants/interactive.js +33 -0
- package/dist/plugins/react-native-a11y/constants/react-native-components.d.ts +7 -0
- package/dist/plugins/react-native-a11y/constants/react-native-components.js +13 -0
- package/dist/plugins/react-native-a11y/constants/schema-properties.d.ts +4 -0
- package/dist/plugins/react-native-a11y/constants/schema-properties.js +10 -0
- package/dist/plugins/react-native-a11y/index.d.ts +3 -0
- package/dist/plugins/react-native-a11y/index.js +48 -0
- package/dist/plugins/react-native-a11y/models/optional-options.d.ts +3 -0
- package/dist/plugins/react-native-a11y/models/optional-options.js +1 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-actions-need-handler.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-actions-need-handler.js +82 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-hint.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-hint.js +74 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-label.d.ts +6 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-label.js +103 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-role.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-role.js +72 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-value-needs-range.d.ts +4 -0
- package/dist/plugins/react-native-a11y/rules/accessibility-value-needs-range.js +49 -0
- package/dist/plugins/react-native-a11y/rules/adjustable-needs-value.d.ts +4 -0
- package/dist/plugins/react-native-a11y/rules/adjustable-needs-value.js +40 -0
- package/dist/plugins/react-native-a11y/rules/gate-announce-for-accessibility.d.ts +4 -0
- package/dist/plugins/react-native-a11y/rules/gate-announce-for-accessibility.js +77 -0
- package/dist/plugins/react-native-a11y/rules/input-purpose.d.ts +6 -0
- package/dist/plugins/react-native-a11y/rules/input-purpose.js +84 -0
- package/dist/plugins/react-native-a11y/rules/no-aria-legacy-mix.d.ts +4 -0
- package/dist/plugins/react-native-a11y/rules/no-aria-legacy-mix.js +57 -0
- package/dist/plugins/react-native-a11y/rules/no-deprecated-accessibility-focus.d.ts +4 -0
- package/dist/plugins/react-native-a11y/rules/no-deprecated-accessibility-focus.js +31 -0
- package/dist/plugins/react-native-a11y/rules/no-dropped-aria-on-text.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/no-dropped-aria-on-text.js +66 -0
- package/dist/plugins/react-native-a11y/rules/no-font-scaling-disabled.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/no-font-scaling-disabled.js +49 -0
- package/dist/plugins/react-native-a11y/rules/no-ineffective-accessibility-props.d.ts +9 -0
- package/dist/plugins/react-native-a11y/rules/no-ineffective-accessibility-props.js +76 -0
- package/dist/plugins/react-native-a11y/rules/no-invalid-accessibility-state.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/no-invalid-accessibility-state.js +82 -0
- package/dist/plugins/react-native-a11y/rules/no-nested-focus-stops.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/no-nested-focus-stops.js +70 -0
- package/dist/plugins/react-native-a11y/rules/no-press-in-activation.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/no-press-in-activation.js +59 -0
- package/dist/plugins/react-native-a11y/rules/no-unnamed-interactive.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/no-unnamed-interactive.js +75 -0
- package/dist/plugins/react-native-a11y/rules/require-invert-colors-opt-out.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/require-invert-colors-opt-out.js +62 -0
- package/dist/plugins/react-native-a11y/rules/require-modal-a11y-on-sheets.d.ts +5 -0
- package/dist/plugins/react-native-a11y/rules/require-modal-a11y-on-sheets.js +55 -0
- package/dist/plugins/react-native-a11y/utils/create-rule.d.ts +5 -0
- package/dist/plugins/react-native-a11y/utils/create-rule.js +3 -0
- package/dist/plugins/react-native-a11y/utils/get-amount-of-text-children.d.ts +8 -0
- package/dist/plugins/react-native-a11y/utils/get-amount-of-text-children.js +67 -0
- package/dist/plugins/react-native-a11y/utils/get-components.d.ts +7 -0
- package/dist/plugins/react-native-a11y/utils/get-components.js +11 -0
- package/dist/plugins/react-native-a11y/utils/get-element-name.d.ts +10 -0
- package/dist/plugins/react-native-a11y/utils/get-element-name.js +66 -0
- package/dist/plugins/react-native-a11y/utils/get-jsx-descendants.d.ts +4 -0
- package/dist/plugins/react-native-a11y/utils/get-jsx-descendants.js +49 -0
- package/dist/plugins/react-native-a11y/utils/get-literal-property-value.d.ts +7 -0
- package/dist/plugins/react-native-a11y/utils/get-literal-property-value.js +21 -0
- package/dist/plugins/react-native-a11y/utils/get-literal-text-child.d.ts +8 -0
- package/dist/plugins/react-native-a11y/utils/get-literal-text-child.js +40 -0
- package/dist/plugins/react-native-a11y/utils/get-property.d.ts +4 -0
- package/dist/plugins/react-native-a11y/utils/get-property.js +9 -0
- package/dist/plugins/react-native-a11y/utils/has-accessible-name.d.ts +6 -0
- package/dist/plugins/react-native-a11y/utils/has-accessible-name.js +27 -0
- package/dist/plugins/react-native-a11y/utils/has-property.d.ts +3 -0
- package/dist/plugins/react-native-a11y/utils/has-property.js +3 -0
- package/dist/plugins/react-native-a11y/utils/has-spread-props.d.ts +8 -0
- package/dist/plugins/react-native-a11y/utils/has-spread-props.js +8 -0
- package/dist/plugins/react-native-a11y/utils/is-definitely-grouped.d.ts +7 -0
- package/dist/plugins/react-native-a11y/utils/is-definitely-grouped.js +20 -0
- package/dist/plugins/react-native-a11y/utils/is-hidden-from-accessibility.d.ts +19 -0
- package/dist/plugins/react-native-a11y/utils/is-hidden-from-accessibility.js +51 -0
- package/dist/plugins/react-native-a11y/utils/is-inside-focus-stop.d.ts +9 -0
- package/dist/plugins/react-native-a11y/utils/is-inside-focus-stop.js +36 -0
- package/dist/plugins/react-native-a11y/utils/is-interactive.d.ts +16 -0
- package/dist/plugins/react-native-a11y/utils/is-interactive.js +38 -0
- package/dist/plugins/react-native-a11y/utils/is-not-a-focus-stop.d.ts +7 -0
- package/dist/plugins/react-native-a11y/utils/is-not-a-focus-stop.js +19 -0
- package/dist/react-native.js +81 -1
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [4.0.0](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/branchCompare?baseVersion=GTv3.0.0&targetVersion=GTv4.0.0) (2026-11-09)
|
|
2
|
+
|
|
3
|
+
### ⚠ BREAKING CHANGES
|
|
4
|
+
|
|
5
|
+
* **react-native:** the react-native preset enables three new error-level rules.
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **react-native:** add Expo rules and Metro asset requires ([059b8c1](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/commit/059b8c1cd8d9bf3772da3b24fc3ed4a91078857a)), closes [AB#5178](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_workitems/edit/5178)
|
|
10
|
+
|
|
11
|
+
## [3.0.0](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/branchCompare?baseVersion=GTv2.0.0&targetVersion=GTv3.0.0) (2026-11-09)
|
|
12
|
+
|
|
13
|
+
### ⚠ BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* **react-native:** the react-native preset enables 18 new error-level
|
|
16
|
+
accessibility rules.
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **react-native:** add in-house react-native-a11y plugin ([755d725](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/commit/755d725061b8c1f09fd8c33b73e2ae0f2b046426)), closes [AB#5177](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_workitems/edit/5177)
|
|
21
|
+
|
|
1
22
|
## [2.0.0](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/branchCompare?baseVersion=GTv1.0.0&targetVersion=GTv2.0.0) (2026-11-09)
|
|
2
23
|
|
|
3
24
|
### ⚠ BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -69,6 +69,17 @@ export default [...base({ tsconfigRootDir: import.meta.dirname }), ...reactNativ
|
|
|
69
69
|
|
|
70
70
|
`react-native` bans browser globals that don't exist on React Native (`document`, `localStorage`, …), and ignores `.expo`, `android`, `ios` and `expo-env.d.ts`.
|
|
71
71
|
|
|
72
|
+
It also enables the bundled `@hypersolid-dev/react-native-a11y` rules, each citing the WCAG criterion it enforces ([rule docs](src/plugins/react-native-a11y/docs)). Custom components are matched by name, so tell a rule about your wrappers with `additionalComponents`. `require-modal-a11y-on-sheets` has no defaults and stays off until you list your sheet components:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
{
|
|
76
|
+
rules: {
|
|
77
|
+
'@hypersolid-dev/react-native-a11y/accessibility-label': ['error', { additionalComponents: ['IconButton'] }],
|
|
78
|
+
'@hypersolid-dev/react-native-a11y/require-modal-a11y-on-sheets': ['error', { additionalComponents: ['BottomSheet'] }],
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
72
83
|
### Next.js
|
|
73
84
|
|
|
74
85
|
```js
|
|
@@ -171,7 +182,7 @@ pnpm lint
|
|
|
171
182
|
pnpm test
|
|
172
183
|
```
|
|
173
184
|
|
|
174
|
-
`pnpm lint` builds the package and lints this repo with its own `base` preset. `pnpm test` runs fixture tests: for each preset, every invalid fixture in `test/fixtures/<preset>/invalid` must trigger the rule it's named after, and the valid fixtures must produce no messages. See [AGENTS.md](AGENTS.md) for conventions.
|
|
185
|
+
`pnpm lint` builds the package and lints this repo with its own `base` preset. `pnpm test` runs fixture tests: for each preset, every invalid fixture in `test/fixtures/<preset>/invalid` must trigger the rule it's named after, and the valid fixtures must produce no messages. Rules this package owns also have `RuleTester` tests in `test/rules`. See [AGENTS.md](AGENTS.md) for conventions.
|
|
175
186
|
|
|
176
187
|
## Versioning
|
|
177
188
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARIA aliases React Native maps onto the legacy `accessibility*` props.
|
|
3
|
+
* Both spellings reach the same native behaviour, so a rule that only knows
|
|
4
|
+
* the legacy name reports a component that is already accessible.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ALIAS_TO_LEGACY: {
|
|
7
|
+
readonly role: "accessibilityRole";
|
|
8
|
+
readonly "aria-label": "accessibilityLabel";
|
|
9
|
+
readonly "aria-labelledby": "accessibilityLabelledBy";
|
|
10
|
+
readonly "aria-live": "accessibilityLiveRegion";
|
|
11
|
+
readonly "aria-modal": "accessibilityViewIsModal";
|
|
12
|
+
readonly "aria-busy": "accessibilityState";
|
|
13
|
+
readonly "aria-checked": "accessibilityState";
|
|
14
|
+
readonly "aria-disabled": "accessibilityState";
|
|
15
|
+
readonly "aria-expanded": "accessibilityState";
|
|
16
|
+
readonly "aria-selected": "accessibilityState";
|
|
17
|
+
readonly "aria-valuemax": "accessibilityValue";
|
|
18
|
+
readonly "aria-valuemin": "accessibilityValue";
|
|
19
|
+
readonly "aria-valuenow": "accessibilityValue";
|
|
20
|
+
readonly "aria-valuetext": "accessibilityValue";
|
|
21
|
+
};
|
|
22
|
+
/** The `accessibilityState` keys their `aria-*` aliases map onto. */
|
|
23
|
+
export declare const STATE_ALIAS_KEYS: Record<string, string>;
|
|
24
|
+
/** The `accessibilityValue` keys their `aria-*` aliases map onto. */
|
|
25
|
+
export declare const VALUE_ALIAS_KEYS: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* `Text` does not implement the ARIA-to-legacy conversion for these, so they
|
|
28
|
+
* are silently dropped rather than reaching the OS.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ARIA_DROPPED_ON_TEXT: string[];
|
|
31
|
+
/** Props that hide a node (or its subtree) from assistive tech. */
|
|
32
|
+
export declare const HIDDEN_PROPS: string[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARIA aliases React Native maps onto the legacy `accessibility*` props.
|
|
3
|
+
* Both spellings reach the same native behaviour, so a rule that only knows
|
|
4
|
+
* the legacy name reports a component that is already accessible.
|
|
5
|
+
*/
|
|
6
|
+
export const ALIAS_TO_LEGACY = {
|
|
7
|
+
role: "accessibilityRole",
|
|
8
|
+
"aria-label": "accessibilityLabel",
|
|
9
|
+
"aria-labelledby": "accessibilityLabelledBy",
|
|
10
|
+
"aria-live": "accessibilityLiveRegion",
|
|
11
|
+
"aria-modal": "accessibilityViewIsModal",
|
|
12
|
+
"aria-busy": "accessibilityState",
|
|
13
|
+
"aria-checked": "accessibilityState",
|
|
14
|
+
"aria-disabled": "accessibilityState",
|
|
15
|
+
"aria-expanded": "accessibilityState",
|
|
16
|
+
"aria-selected": "accessibilityState",
|
|
17
|
+
"aria-valuemax": "accessibilityValue",
|
|
18
|
+
"aria-valuemin": "accessibilityValue",
|
|
19
|
+
"aria-valuenow": "accessibilityValue",
|
|
20
|
+
"aria-valuetext": "accessibilityValue",
|
|
21
|
+
};
|
|
22
|
+
/** The `accessibilityState` keys their `aria-*` aliases map onto. */
|
|
23
|
+
export const STATE_ALIAS_KEYS = {
|
|
24
|
+
"aria-busy": "busy",
|
|
25
|
+
"aria-checked": "checked",
|
|
26
|
+
"aria-disabled": "disabled",
|
|
27
|
+
"aria-expanded": "expanded",
|
|
28
|
+
"aria-selected": "selected",
|
|
29
|
+
};
|
|
30
|
+
/** The `accessibilityValue` keys their `aria-*` aliases map onto. */
|
|
31
|
+
export const VALUE_ALIAS_KEYS = {
|
|
32
|
+
"aria-valuemax": "max",
|
|
33
|
+
"aria-valuemin": "min",
|
|
34
|
+
"aria-valuenow": "now",
|
|
35
|
+
"aria-valuetext": "text",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* `Text` does not implement the ARIA-to-legacy conversion for these, so they
|
|
39
|
+
* are silently dropped rather than reaching the OS.
|
|
40
|
+
*/
|
|
41
|
+
export const ARIA_DROPPED_ON_TEXT = [
|
|
42
|
+
"aria-labelledby",
|
|
43
|
+
"aria-live",
|
|
44
|
+
"aria-valuemax",
|
|
45
|
+
"aria-valuemin",
|
|
46
|
+
"aria-valuenow",
|
|
47
|
+
"aria-valuetext",
|
|
48
|
+
];
|
|
49
|
+
/** Props that hide a node (or its subtree) from assistive tech. */
|
|
50
|
+
export const HIDDEN_PROPS = ["aria-hidden", "accessibilityElementsHidden"];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Roles that imply the node is operated, so it owes an accessible name. */
|
|
2
|
+
export declare const INTERACTIVE_ROLES: string[];
|
|
3
|
+
/** Props through which a node handles a touch itself. */
|
|
4
|
+
export declare const TOUCH_HANDLER_PROPS: string[];
|
|
5
|
+
/** Props that can supply an accessible name. */
|
|
6
|
+
export declare const NAME_PROPS: string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Roles that imply the node is operated, so it owes an accessible name. */
|
|
2
|
+
export const INTERACTIVE_ROLES = [
|
|
3
|
+
"button",
|
|
4
|
+
"checkbox",
|
|
5
|
+
"combobox",
|
|
6
|
+
"imagebutton",
|
|
7
|
+
"link",
|
|
8
|
+
"menuitem",
|
|
9
|
+
"radio",
|
|
10
|
+
"search",
|
|
11
|
+
"spinbutton",
|
|
12
|
+
"switch",
|
|
13
|
+
"tab",
|
|
14
|
+
"togglebutton",
|
|
15
|
+
"adjustable",
|
|
16
|
+
];
|
|
17
|
+
/** Props through which a node handles a touch itself. */
|
|
18
|
+
export const TOUCH_HANDLER_PROPS = [
|
|
19
|
+
"onPress",
|
|
20
|
+
"onPressIn",
|
|
21
|
+
"onPressOut",
|
|
22
|
+
"onLongPress",
|
|
23
|
+
"onTouchStart",
|
|
24
|
+
"onTouchEnd",
|
|
25
|
+
"onAccessibilityTap",
|
|
26
|
+
];
|
|
27
|
+
/** Props that can supply an accessible name. */
|
|
28
|
+
export const NAME_PROPS = [
|
|
29
|
+
"accessibilityLabel",
|
|
30
|
+
"aria-label",
|
|
31
|
+
"accessibilityLabelledBy",
|
|
32
|
+
"aria-labelledby",
|
|
33
|
+
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const IMAGES: string[];
|
|
2
|
+
export declare const TEXT_INPUTS: string[];
|
|
3
|
+
export declare const INPUTS: string[];
|
|
4
|
+
export declare const TEXTS: string[];
|
|
5
|
+
/** Hosts that are not accessibility elements until something opts them in. */
|
|
6
|
+
export declare const VIEWS: string[];
|
|
7
|
+
export declare const TOUCHABLES: string[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const IMAGES = ["Image", "ImageBackground"];
|
|
2
|
+
export const TEXT_INPUTS = ["TextInput"];
|
|
3
|
+
export const INPUTS = [...TEXT_INPUTS, "Switch"];
|
|
4
|
+
export const TEXTS = ["Text"];
|
|
5
|
+
/** Hosts that are not accessibility elements until something opts them in. */
|
|
6
|
+
export const VIEWS = ["View", "ScrollView", "SafeAreaView"];
|
|
7
|
+
export const TOUCHABLES = [
|
|
8
|
+
"Pressable",
|
|
9
|
+
"TouchableOpacity",
|
|
10
|
+
"TouchableHighlight",
|
|
11
|
+
"TouchableWithoutFeedback",
|
|
12
|
+
"TouchableNativeFeedback",
|
|
13
|
+
];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import accessibilityActionsNeedHandler from "./rules/accessibility-actions-need-handler.js";
|
|
2
|
+
import accessibilityHint from "./rules/accessibility-hint.js";
|
|
3
|
+
import accessibilityLabel from "./rules/accessibility-label.js";
|
|
4
|
+
import accessibilityRole from "./rules/accessibility-role.js";
|
|
5
|
+
import accessibilityValueNeedsRange from "./rules/accessibility-value-needs-range.js";
|
|
6
|
+
import adjustableNeedsValue from "./rules/adjustable-needs-value.js";
|
|
7
|
+
import gateAnnounceForAccessibility from "./rules/gate-announce-for-accessibility.js";
|
|
8
|
+
import inputPurpose from "./rules/input-purpose.js";
|
|
9
|
+
import noAriaLegacyMix from "./rules/no-aria-legacy-mix.js";
|
|
10
|
+
import noDeprecatedAccessibilityFocus from "./rules/no-deprecated-accessibility-focus.js";
|
|
11
|
+
import noDroppedAriaOnText from "./rules/no-dropped-aria-on-text.js";
|
|
12
|
+
import noFontScalingDisabled from "./rules/no-font-scaling-disabled.js";
|
|
13
|
+
import noIneffectiveAccessibilityProps from "./rules/no-ineffective-accessibility-props.js";
|
|
14
|
+
import noInvalidAccessibilityState from "./rules/no-invalid-accessibility-state.js";
|
|
15
|
+
import noNestedFocusStops from "./rules/no-nested-focus-stops.js";
|
|
16
|
+
import noPressInActivation from "./rules/no-press-in-activation.js";
|
|
17
|
+
import noUnnamedInteractive from "./rules/no-unnamed-interactive.js";
|
|
18
|
+
import requireInvertColorsOptOut from "./rules/require-invert-colors-opt-out.js";
|
|
19
|
+
import requireModalA11yOnSheets from "./rules/require-modal-a11y-on-sheets.js";
|
|
20
|
+
const rules = {
|
|
21
|
+
"accessibility-actions-need-handler": accessibilityActionsNeedHandler,
|
|
22
|
+
"accessibility-hint": accessibilityHint,
|
|
23
|
+
"accessibility-label": accessibilityLabel,
|
|
24
|
+
"accessibility-role": accessibilityRole,
|
|
25
|
+
"accessibility-value-needs-range": accessibilityValueNeedsRange,
|
|
26
|
+
"adjustable-needs-value": adjustableNeedsValue,
|
|
27
|
+
"gate-announce-for-accessibility": gateAnnounceForAccessibility,
|
|
28
|
+
"input-purpose": inputPurpose,
|
|
29
|
+
"no-aria-legacy-mix": noAriaLegacyMix,
|
|
30
|
+
"no-deprecated-accessibility-focus": noDeprecatedAccessibilityFocus,
|
|
31
|
+
"no-dropped-aria-on-text": noDroppedAriaOnText,
|
|
32
|
+
"no-font-scaling-disabled": noFontScalingDisabled,
|
|
33
|
+
"no-ineffective-accessibility-props": noIneffectiveAccessibilityProps,
|
|
34
|
+
"no-invalid-accessibility-state": noInvalidAccessibilityState,
|
|
35
|
+
"no-nested-focus-stops": noNestedFocusStops,
|
|
36
|
+
"no-press-in-activation": noPressInActivation,
|
|
37
|
+
"no-unnamed-interactive": noUnnamedInteractive,
|
|
38
|
+
"require-invert-colors-opt-out": requireInvertColorsOptOut,
|
|
39
|
+
"require-modal-a11y-on-sheets": requireModalA11yOnSheets,
|
|
40
|
+
};
|
|
41
|
+
// React Native accessibility rules, maintained in-house. Originally @hypersolid-dev/eslint-config (react-native preset) v2.2.0 (MIT), from
|
|
42
|
+
// before Triple became Hypersolid. Rule docs live in ./docs.
|
|
43
|
+
const plugin = {
|
|
44
|
+
meta: { name: "@hypersolid-dev/react-native-a11y" },
|
|
45
|
+
rules,
|
|
46
|
+
};
|
|
47
|
+
// typescript-eslint rule types are stricter than ESLint's `Plugin` type and not assignable to it; the runtime shape is the same.
|
|
48
|
+
export default plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
type MessageIds = "actionsWithoutHandler" | "handlerWithoutActions" | "customActionWithoutLabel";
|
|
2
|
+
declare const accessibilityActionsNeedHandler: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default accessibilityActionsNeedHandler;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
2
|
+
import createRule from "../utils/create-rule.js";
|
|
3
|
+
import getProperty from "../utils/get-property.js";
|
|
4
|
+
import hasSpreadProps from "../utils/has-spread-props.js";
|
|
5
|
+
/** Names the platforms already describe to the user. */
|
|
6
|
+
const STANDARD_ACTIONS = new Set([
|
|
7
|
+
"activate",
|
|
8
|
+
"increment",
|
|
9
|
+
"decrement",
|
|
10
|
+
"longpress",
|
|
11
|
+
"magicTap",
|
|
12
|
+
"escape",
|
|
13
|
+
]);
|
|
14
|
+
const accessibilityActionsNeedHandler = createRule({
|
|
15
|
+
name: "accessibility-actions-need-handler",
|
|
16
|
+
meta: {
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
type: "problem",
|
|
19
|
+
docs: {
|
|
20
|
+
description: "Enforce accessibilityActions and onAccessibilityAction are declared together.",
|
|
21
|
+
},
|
|
22
|
+
messages: {
|
|
23
|
+
actionsWithoutHandler: "'accessibilityActions' are offered to assistive tech but nothing handles them. Add 'onAccessibilityAction'.",
|
|
24
|
+
handlerWithoutActions: "'onAccessibilityAction' never fires without 'accessibilityActions' to declare what can be invoked.",
|
|
25
|
+
customActionWithoutLabel: "The custom action '{{ name }}' has no 'label', so assistive tech lists it by its internal name. Add a label users can read.",
|
|
26
|
+
},
|
|
27
|
+
schema: [],
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
const reportUnlabelledActions = (expression) => {
|
|
31
|
+
if (expression.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const element of expression.elements) {
|
|
35
|
+
if (element?.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const entries = element.properties.flatMap((property) => property.type === AST_NODE_TYPES.Property &&
|
|
39
|
+
property.key.type === AST_NODE_TYPES.Identifier
|
|
40
|
+
? [[property.key.name, property.value]]
|
|
41
|
+
: []);
|
|
42
|
+
const name = entries.find(([key]) => key === "name")?.[1];
|
|
43
|
+
if (name?.type !== AST_NODE_TYPES.Literal ||
|
|
44
|
+
typeof name.value !== "string" ||
|
|
45
|
+
STANDARD_ACTIONS.has(name.value) ||
|
|
46
|
+
entries.some(([key]) => key === "label")) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
context.report({
|
|
50
|
+
node: element,
|
|
51
|
+
messageId: "customActionWithoutLabel",
|
|
52
|
+
data: { name: name.value },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
JSXOpeningElement(node) {
|
|
58
|
+
if (hasSpreadProps(node)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const actions = getProperty(node, "accessibilityActions");
|
|
62
|
+
const handler = getProperty(node, "onAccessibilityAction");
|
|
63
|
+
if (actions && !handler) {
|
|
64
|
+
context.report({
|
|
65
|
+
node: actions,
|
|
66
|
+
messageId: "actionsWithoutHandler",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (actions?.value?.type === AST_NODE_TYPES.JSXExpressionContainer) {
|
|
70
|
+
reportUnlabelledActions(actions.value.expression);
|
|
71
|
+
}
|
|
72
|
+
if (handler && !actions) {
|
|
73
|
+
context.report({
|
|
74
|
+
node: handler,
|
|
75
|
+
messageId: "handlerWithoutActions",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
export default accessibilityActionsNeedHandler;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Options } from "../models/optional-options.js";
|
|
2
|
+
declare const accessibilityHint: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingAccessibilityLabel", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default accessibilityHint;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { TOUCHABLES } from "../constants/react-native-components.js";
|
|
2
|
+
import schemaProperties from "../constants/schema-properties.js";
|
|
3
|
+
import createRule from "../utils/create-rule.js";
|
|
4
|
+
import getAmountOfTextChildren from "../utils/get-amount-of-text-children.js";
|
|
5
|
+
import getComponents from "../utils/get-components.js";
|
|
6
|
+
import getElementName from "../utils/get-element-name.js";
|
|
7
|
+
import { hasLabelProperty as hasLabelProperty } from "../utils/has-accessible-name.js";
|
|
8
|
+
import hasProperty from "../utils/has-property.js";
|
|
9
|
+
import hasSpreadProps from "../utils/has-spread-props.js";
|
|
10
|
+
import isHiddenFromAccessibility from "../utils/is-hidden-from-accessibility.js";
|
|
11
|
+
import isInsideFocusStop from "../utils/is-inside-focus-stop.js";
|
|
12
|
+
import isNotAFocusStop from "../utils/is-not-a-focus-stop.js";
|
|
13
|
+
const accessibilityHint = createRule({
|
|
14
|
+
name: "accessibility-hint",
|
|
15
|
+
meta: {
|
|
16
|
+
defaultOptions: [
|
|
17
|
+
{
|
|
18
|
+
additionalComponents: [],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
type: "problem",
|
|
22
|
+
docs: {
|
|
23
|
+
description: "Enforce a hint is paired with an accessible name, since a hint is not a name.",
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
missingAccessibilityLabel: "The '<{{ componentName }} />' component must have an 'accessibilityLabel' in order to use the 'accessibilityHint'.",
|
|
27
|
+
},
|
|
28
|
+
schema: [
|
|
29
|
+
{
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: schemaProperties,
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
create(context) {
|
|
37
|
+
const components = getComponents({
|
|
38
|
+
context,
|
|
39
|
+
defaultComponents: TOUCHABLES,
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
JSXOpeningElement(node) {
|
|
43
|
+
const componentName = getElementName(node);
|
|
44
|
+
if (componentName === undefined) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!components.includes(componentName)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!hasProperty(node, "accessibilityHint") ||
|
|
51
|
+
hasSpreadProps(node) ||
|
|
52
|
+
isHiddenFromAccessibility(node) ||
|
|
53
|
+
isNotAFocusStop(node) ||
|
|
54
|
+
isInsideFocusStop(node)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Visible text already names the control, so the hint has a name to
|
|
58
|
+
// hang off even without a label
|
|
59
|
+
const { children } = node.parent;
|
|
60
|
+
if (hasLabelProperty(node) || getAmountOfTextChildren(children) > 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
context.report({
|
|
64
|
+
node,
|
|
65
|
+
messageId: "missingAccessibilityLabel",
|
|
66
|
+
data: {
|
|
67
|
+
componentName,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
export default accessibilityHint;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Options } from "../models/optional-options.js";
|
|
2
|
+
type MessageIds = "missingAccessibilityLabel" | "emptyAccessibilityLabel" | "labelMissingVisibleText";
|
|
3
|
+
declare const accessibilityLabel: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
export default accessibilityLabel;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { IMAGES, INPUTS, TOUCHABLES } from "../constants/react-native-components.js";
|
|
2
|
+
import schemaProperties from "../constants/schema-properties.js";
|
|
3
|
+
import createRule from "../utils/create-rule.js";
|
|
4
|
+
import getAmountOfTextChildren from "../utils/get-amount-of-text-children.js";
|
|
5
|
+
import getComponents from "../utils/get-components.js";
|
|
6
|
+
import getElementName from "../utils/get-element-name.js";
|
|
7
|
+
import { getLiteralLabel, hasEmptyLabel, hasLabelProperty as hasLabelProperty, } from "../utils/has-accessible-name.js";
|
|
8
|
+
import getLiteralTextChild from "../utils/get-literal-text-child.js";
|
|
9
|
+
import hasSpreadProps from "../utils/has-spread-props.js";
|
|
10
|
+
import isHiddenFromAccessibility from "../utils/is-hidden-from-accessibility.js";
|
|
11
|
+
import isInsideFocusStop from "../utils/is-inside-focus-stop.js";
|
|
12
|
+
import isNotAFocusStop from "../utils/is-not-a-focus-stop.js";
|
|
13
|
+
const accessibilityLabel = createRule({
|
|
14
|
+
name: "accessibility-label",
|
|
15
|
+
meta: {
|
|
16
|
+
defaultOptions: [
|
|
17
|
+
{
|
|
18
|
+
additionalComponents: [],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
type: "problem",
|
|
22
|
+
docs: {
|
|
23
|
+
description: "Enforce accessibilityLabel prop is present on controls without visible text.",
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
missingAccessibilityLabel: "The '<{{ componentName }} />' component must have an 'accessibilityLabel' prop.",
|
|
27
|
+
labelMissingVisibleText: "The label '{{ label }}' does not contain the visible text '{{ text }}', so a Voice Control user saying what they can see will not reach this control.",
|
|
28
|
+
emptyAccessibilityLabel: "The '<{{ componentName }} />' component has an empty 'accessibilityLabel', which leaves it unnamed. Give it a name or hide it with 'aria-hidden'.",
|
|
29
|
+
},
|
|
30
|
+
schema: [
|
|
31
|
+
{
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: schemaProperties,
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const components = getComponents({
|
|
40
|
+
context,
|
|
41
|
+
defaultComponents: [...IMAGES, ...TOUCHABLES, ...INPUTS],
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
JSXOpeningElement(node) {
|
|
45
|
+
const componentName = getElementName(node);
|
|
46
|
+
if (componentName === undefined) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!components.includes(componentName)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Decorative content is hidden rather than labelled
|
|
53
|
+
// The surface above answers for its children
|
|
54
|
+
if (isHiddenFromAccessibility(node) ||
|
|
55
|
+
isNotAFocusStop(node) ||
|
|
56
|
+
isInsideFocusStop(node)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (hasEmptyLabel(node)) {
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
messageId: "emptyAccessibilityLabel",
|
|
63
|
+
data: {
|
|
64
|
+
componentName,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// The label may arrive through the spread
|
|
70
|
+
if (hasSpreadProps(node)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Visible text already names the control
|
|
74
|
+
const { children } = node.parent;
|
|
75
|
+
const amountOfTextChildren = getAmountOfTextChildren(children);
|
|
76
|
+
// 2.5.3: a spoken command matches the accessible name, so the name
|
|
77
|
+
// has to contain what the user can read
|
|
78
|
+
const label = getLiteralLabel(node);
|
|
79
|
+
const visible = getLiteralTextChild(children);
|
|
80
|
+
if (label !== undefined &&
|
|
81
|
+
visible !== undefined &&
|
|
82
|
+
!label.toLowerCase().includes(visible.toLowerCase())) {
|
|
83
|
+
context.report({
|
|
84
|
+
node,
|
|
85
|
+
messageId: "labelMissingVisibleText",
|
|
86
|
+
data: { label, text: visible },
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!hasLabelProperty(node) && amountOfTextChildren === 0) {
|
|
91
|
+
context.report({
|
|
92
|
+
node,
|
|
93
|
+
messageId: "missingAccessibilityLabel",
|
|
94
|
+
data: {
|
|
95
|
+
componentName,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
export default accessibilityLabel;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Options } from "../models/optional-options.js";
|
|
2
|
+
declare const accessibilityRole: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingAccessibilityRole", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default accessibilityRole;
|