@hypersolid-dev/eslint-config 1.0.0 → 3.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 +23 -0
- package/README.md +28 -1
- package/dist/base.d.ts +1 -0
- package/dist/base.js +63 -8
- 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 +85 -1
- package/dist/shared.d.ts +8 -0
- package/dist/shared.js +22 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
## [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)
|
|
2
|
+
|
|
3
|
+
### ⚠ BREAKING CHANGES
|
|
4
|
+
|
|
5
|
+
* **react-native:** the react-native preset enables 18 new error-level
|
|
6
|
+
accessibility rules.
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **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)
|
|
11
|
+
|
|
12
|
+
## [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)
|
|
13
|
+
|
|
14
|
+
### ⚠ BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* **base:** switch-exhaustiveness-check now accepts a default branch as exhaustive.
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **base:** retune rules after the ESPN Goal rule review ([113775e](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/commit/113775ed1b1ae3abfcac948d55ba33328311b903)), closes [AB#5176](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_workitems/edit/5176)
|
|
21
|
+
* **react-native:** Reanimated boolean wrappers, Expo no-secrets ignores, browser globals in tests ([86bf9b3](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/commit/86bf9b3149a0696b4cc9114ca7f60c5074c002c0)), closes [AB#5176](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_workitems/edit/5176)
|
|
22
|
+
* **react:** exempt hook and mock names from consistent-boolean-name, relax use-prefix in tests ([2d96538](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/commit/2d96538f941a9b5cf28b28c9481875b3da2f6a67)), closes [AB#5176](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_workitems/edit/5176)
|
|
23
|
+
|
|
1
24
|
## [1.0.0](https://dev.azure.com/wearetriple/Triple%20-%20Webteam/_git/eslint-config-hypersolid/branchCompare?baseVersion=GTv0.1.2&targetVersion=GTv1.0.0) (2026-11-06)
|
|
2
25
|
|
|
3
26
|
### ⚠ 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
|
|
@@ -124,6 +135,22 @@ export default [...base({ tsconfigRootDir: import.meta.dirname }), { ignores: ['
|
|
|
124
135
|
|
|
125
136
|
`base` already ignores `node_modules`, `dist`, `build`, `coverage` and lockfiles.
|
|
126
137
|
|
|
138
|
+
### Test files
|
|
139
|
+
|
|
140
|
+
Every preset relaxes a few rules for tests, stories, mocks and test setup (`TEST_FILES`, exported from `@hypersolid-dev/eslint-config/base`): `@typescript-eslint/no-unsafe-assignment`, `no-unsafe-member-access`, `no-invalid-void-type`, `unicorn/no-error-property-assignment` and `no-global-object-property-assignment` in `base`; `@eslint-react/no-unnecessary-use-prefix` in `react` and `react-native`; and the browser-globals list (`no-restricted-globals`) in `react-native`. Source code keeps them all.
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
import base, { TEST_FILES } from '@hypersolid-dev/eslint-config/base';
|
|
144
|
+
|
|
145
|
+
export default [...base({ tsconfigRootDir: import.meta.dirname }), { files: TEST_FILES, rules: { 'no-console': 'off' } }];
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Preset-specific options
|
|
149
|
+
|
|
150
|
+
- `unicorn/consistent-boolean-name` skips function names in every preset; `react` and `react-native` also skip hook (`^use[A-Z]`) and jest mock (`^mock`) names, and `react-native` reads Reanimated's `SharedValue` through `.value`.
|
|
151
|
+
- `no-secrets/no-secrets` ignores `data:` URIs in JSON; in `react-native` it also ignores Expo and native identifiers (`EXPO_*`, `android.settings.*`, `@xml…`, `NSPrivacy*`, i18n `Namespace/Key` paths) in code and JSON.
|
|
152
|
+
- `@typescript-eslint/switch-exhaustiveness-check` accepts a `default` branch as exhaustive (`considerDefaultExhaustiveForUnions`). Set it to `false` to make every new union member fail its switches.
|
|
153
|
+
|
|
127
154
|
## Formatting and import order
|
|
128
155
|
|
|
129
156
|
This config has no formatting or stylistic rules, and it doesn't sort imports (`import-x/order` is off). Use a formatter such as Prettier or Biome for both.
|
|
@@ -155,7 +182,7 @@ pnpm lint
|
|
|
155
182
|
pnpm test
|
|
156
183
|
```
|
|
157
184
|
|
|
158
|
-
`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.
|
|
159
186
|
|
|
160
187
|
## Versioning
|
|
161
188
|
|
package/dist/base.d.ts
CHANGED
package/dist/base.js
CHANGED
|
@@ -5,7 +5,9 @@ import { createNodeResolver, flatConfigs as importX } from 'eslint-plugin-import
|
|
|
5
5
|
import noSecrets from 'eslint-plugin-no-secrets';
|
|
6
6
|
import unicorn from 'eslint-plugin-unicorn';
|
|
7
7
|
import { configs as tsConfigs } from 'typescript-eslint';
|
|
8
|
-
import { CODE_FILES, NUMBER_GLOBAL_RESTRICTIONS, raiseWarnings, scopeToCode } from './shared.js';
|
|
8
|
+
import { BOOLEAN_NAME_OPTIONS, CODE_FILES, NUMBER_GLOBAL_RESTRICTIONS, raiseWarnings, scopeToCode, TEST_FILES } from './shared.js';
|
|
9
|
+
// The glob the presets relax test-only rules for; reuse it for your own test overrides.
|
|
10
|
+
export { TEST_FILES } from './shared.js';
|
|
9
11
|
export default function base(options) {
|
|
10
12
|
if (!options.tsconfigRootDir) {
|
|
11
13
|
throw new Error('@hypersolid-dev/eslint-config: base() requires tsconfigRootDir');
|
|
@@ -66,16 +68,23 @@ export default function base(options) {
|
|
|
66
68
|
'@typescript-eslint/no-import-type-side-effects': 'error',
|
|
67
69
|
// `??` over `||` for defaults, so `0` and `''` survive. Bad: `count || 10`.
|
|
68
70
|
'@typescript-eslint/prefer-nullish-coalescing': 'error',
|
|
69
|
-
//
|
|
70
|
-
'@typescript-eslint/
|
|
71
|
+
// A void call as an expression; arrow shorthand like `onPress={() => setOpen(true)}` is fine. Bad: `const result = log(x)`.
|
|
72
|
+
'@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
|
|
73
|
+
// `sort()` needs a comparator, except on strings, where the default order is correct. Bad: `[10, 9].sort()`.
|
|
74
|
+
'@typescript-eslint/require-array-sort-compare': ['error', { ignoreStringArrays: true }],
|
|
75
|
+
// Only strings and numbers in template literals (strictTypeChecked's options plus numbers). Bad: `` `${user}` ``, `` `${null}` ``.
|
|
76
|
+
'@typescript-eslint/restrict-template-expressions': [
|
|
77
|
+
'error',
|
|
78
|
+
{ allowAny: false, allowBoolean: false, allowNever: false, allowNullish: false, allowNumber: true, allowRegExp: false },
|
|
79
|
+
],
|
|
71
80
|
// No value returned where `void` is expected. Bad: `run(() => 1)` with `run(callback: () => void)`.
|
|
72
81
|
'@typescript-eslint/strict-void-return': ['error', { allowReturnAny: true }],
|
|
73
|
-
// A `switch` on a union handles every member
|
|
74
|
-
'@typescript-eslint/switch-exhaustiveness-check': 'error',
|
|
82
|
+
// A `switch` on a union handles every member or has a `default`. Bad: `switch (shape) { case 'circle': … }` missing `'square'`.
|
|
83
|
+
'@typescript-eslint/switch-exhaustiveness-check': ['error', { considerDefaultExhaustiveForUnions: true }],
|
|
75
84
|
// The caught value is named `error`. Bad: `catch (e)`.
|
|
76
85
|
'unicorn/catch-error-name': 'error',
|
|
77
86
|
// A boolean reads as a claim. Bad: `const visible = true` (use `isVisible`).
|
|
78
|
-
'unicorn/consistent-boolean-name': ['error',
|
|
87
|
+
'unicorn/consistent-boolean-name': ['error', BOOLEAN_NAME_OPTIONS],
|
|
79
88
|
// A compound word is one word. Bad: `callBack`, `dataBase`.
|
|
80
89
|
'unicorn/consistent-compound-words': 'error',
|
|
81
90
|
// No non-standard abbreviations. Bad: `btn`, `cfg`. Allowed: `props`, `args`, `fn`, `ref`, `env`, `params`.
|
|
@@ -99,12 +108,58 @@ export default function base(options) {
|
|
|
99
108
|
'unicorn/no-unsafe-promise-all-settled-values': 'error',
|
|
100
109
|
// An async function already wraps its return value. Bad: `async () => Promise.resolve(1)`.
|
|
101
110
|
'unicorn/no-useless-promise-resolve-reject': 'error',
|
|
111
|
+
// `undefined` as an argument is often required by the callee's signature. Bad: `let value = undefined`.
|
|
112
|
+
'unicorn/no-useless-undefined': ['error', { checkArguments: false }],
|
|
113
|
+
// Off: `.then()` chains are fine. Bad: `fetchUser().then(render)` reported.
|
|
114
|
+
'unicorn/prefer-await': 'off',
|
|
115
|
+
// Off: an `if`/`else` is often clearer than a ternary. Bad: `if (a) { x = 1 } else { x = 2 }` reported.
|
|
116
|
+
'unicorn/prefer-ternary': 'off',
|
|
117
|
+
// Off: a `case` next to `default` documents a known value. Bad: `case 'other': default:` reported.
|
|
118
|
+
'unicorn/no-useless-switch-case': 'off',
|
|
119
|
+
// Off: @typescript-eslint/require-array-sort-compare reports it with type information. Bad: `[10, 9].sort()` reported twice.
|
|
120
|
+
'unicorn/require-array-sort-compare': 'off',
|
|
121
|
+
// Off: DOM traversal style is taste. Bad: `element.parentNode` reported.
|
|
122
|
+
'unicorn/better-dom-traversing': 'off',
|
|
123
|
+
// Off: import style is taste. Bad: `import { join } from 'node:path'` reported.
|
|
124
|
+
'unicorn/import-style': 'off',
|
|
125
|
+
// Off: skipping elements by commas is fine. Bad: `const [, second] = list` reported.
|
|
126
|
+
'unicorn/no-unreadable-array-destructuring': 'off',
|
|
127
|
+
// Off: nested destructuring is fine. Bad: `const { a: { b } } = value` reported.
|
|
128
|
+
'unicorn/no-unreadable-object-destructuring': 'off',
|
|
129
|
+
// Off: `1.0` can document a float. Bad: `const opacity = 1.0` reported.
|
|
130
|
+
'unicorn/no-zero-fractions': 'off',
|
|
131
|
+
// Off: rarely relevant, noisy on intentional characters. Bad: a literal no-break space reported.
|
|
132
|
+
'unicorn/prefer-escaped-irregular-whitespace': 'off',
|
|
133
|
+
// Off: `window` and `global` are clearer where the platform is known. Bad: `window.location` reported.
|
|
134
|
+
'unicorn/prefer-global-this': 'off',
|
|
135
|
+
// Off: escaped strings are fine. Bad: `'C:\\temp'` reported.
|
|
136
|
+
'unicorn/prefer-string-raw': 'off',
|
|
137
|
+
// Off: rarely relevant. Bad: `Array(3).fill('-').join('')` reported.
|
|
138
|
+
'unicorn/prefer-string-repeat': 'off',
|
|
102
139
|
// Import order is formatting, out of scope (AGENTS.md).
|
|
103
140
|
'import-x/order': 'off',
|
|
141
|
+
// Off: TypeScript checks namespace members, and the rule is slow. Bad: `import * as math from './math.js'; math.missing` reported twice.
|
|
142
|
+
'import-x/namespace': 'off',
|
|
104
143
|
// High-entropy strings that look like credentials. Bad: `const token = 'ghp_Zx8kQ2vLm9…'`.
|
|
105
144
|
'no-secrets/no-secrets': 'error',
|
|
106
145
|
},
|
|
107
146
|
},
|
|
147
|
+
{
|
|
148
|
+
name: '@hypersolid-dev/base/tests',
|
|
149
|
+
files: TEST_FILES,
|
|
150
|
+
rules: {
|
|
151
|
+
// Off: mocks and fixtures are loosely typed by design. Bad: `const user = jest.fn()()` reported.
|
|
152
|
+
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
153
|
+
// Off: mocks and fixtures are loosely typed by design. Bad: `mock.calls[0].user` reported.
|
|
154
|
+
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
155
|
+
// Off: mock signatures use `void` as a generic. Bad: `jest.fn<void, []>()` reported.
|
|
156
|
+
'@typescript-eslint/no-invalid-void-type': 'off',
|
|
157
|
+
// Off: tests build errors by hand. Bad: `error.code = 'ENOENT'` reported.
|
|
158
|
+
'unicorn/no-error-property-assignment': 'off',
|
|
159
|
+
// Off: test setup stubs globals. Bad: `globalThis.fetch = mockFetch` reported.
|
|
160
|
+
'unicorn/no-global-object-property-assignment': 'off',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
108
163
|
{
|
|
109
164
|
name: '@hypersolid-dev/base/json',
|
|
110
165
|
files: ['**/*.json'],
|
|
@@ -113,8 +168,8 @@ export default function base(options) {
|
|
|
113
168
|
rules: {
|
|
114
169
|
// JSON correctness. Bad: `{ "name": "a", "name": "b" }`.
|
|
115
170
|
...json.configs.recommended.rules,
|
|
116
|
-
// Letters-only strings are keys and words (`verbatimModuleSyntax`),
|
|
117
|
-
'no-secrets/no-secrets': ['error', { ignoreContent: ['^[A-Za-z]+$'] }],
|
|
171
|
+
// Letters-only strings are keys and words (`verbatimModuleSyntax`), and `data:` URIs are embedded images; neither is a credential.
|
|
172
|
+
'no-secrets/no-secrets': ['error', { ignoreContent: ['^[A-Za-z]+$', '^data:'] }],
|
|
118
173
|
},
|
|
119
174
|
},
|
|
120
175
|
]);
|
|
@@ -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;
|