@drupal-canvas/eslint-config 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +17 -8
  2. package/dist/index.js +204 -179
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -4,11 +4,12 @@ ESLint config for validating Drupal Canvas Code Components.
4
4
 
5
5
  ## Config variants
6
6
 
7
- | Config | Description |
8
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
- | `required` | Base settings for parsing JS/JSX files, YAML parsing for `component.yml` files, and custom rules for Drupal Canvas Code Component validation. Automatically used by [`@drupal-canvas/cli`](https://www.npmjs.com/package/@drupal-canvas/cli) when validating, building, or uploading components. |
10
- | `recommended` | `required` + recommended rules from [`@eslint/js`](https://www.npmjs.com/package/@eslint/js), [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react), [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks), [`eslint-plugin-jsx-a11y`](https://www.npmjs.com/package/eslint-plugin-jsx-a11y), and [`eslint-plugin-yml`](https://www.npmjs.com/package/eslint-plugin-yml). |
11
- | `strict` | `recommended` + strict rules from [`eslint-plugin-jsx-a11y`](https://www.npmjs.com/package/eslint-plugin-jsx-a11y). |
7
+ | Config | Description |
8
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
9
+ | `required` | Base settings for parsing JavaScript and TypeScript files, YAML parsing for component metadata files, and custom rules for Drupal Canvas Code Component validation. Automatically used by [`@drupal-canvas/cli`](https://www.npmjs.com/package/@drupal-canvas/cli) when validating, building, or uploading components. |
10
+ | `requiredDeprecated` | @deprecated Legacy version of the `required` config used by `build-d` command of [`@drupal-canvas/cli`](https://www.npmjs.com/package/@drupal-canvas/cli#deprecated-build-d). |
11
+ | `recommended` | `required` + recommended rules from [`@eslint/js`](https://www.npmjs.com/package/@eslint/js), [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react), [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks), [`eslint-plugin-jsx-a11y`](https://www.npmjs.com/package/eslint-plugin-jsx-a11y), [`eslint-plugin-yml`](https://www.npmjs.com/package/eslint-plugin-yml) and [`typescript-eslint`](https://www.npmjs.com/package/typescript-eslint). |
12
+ | `strict` | `recommended` + strict rules from [`eslint-plugin-jsx-a11y`](https://www.npmjs.com/package/eslint-plugin-jsx-a11y) and [`typescript-eslint`](https://www.npmjs.com/package/typescript-eslint). |
12
13
 
13
14
  ## Usage
14
15
 
@@ -32,14 +33,22 @@ export default defineConfig([
32
33
  The following custom rules are part of the `required` config and validate Drupal
33
34
  Canvas Code Components:
34
35
 
36
+ | Rule | Description |
37
+ | ---------------------- | ------------------------------------------------------------------------------------------------------- |
38
+ | `component-dir-name` | Validates that `machineName` matches the directory name (index-style) or filename prefix (named-style). |
39
+ | `component-exports` | Validates that component has a default export. |
40
+ | `component-prop-names` | Validates that component prop IDs match the camelCase version of their titles. |
41
+
42
+ ### Deprecated rules
43
+
44
+ The following rules are deprecated and only used in the `requiredDeprecated`
45
+ config:
46
+
35
47
  | Rule | Description |
36
48
  | ------------------------ | ----------------------------------------------------------------------------------------- |
37
- | `component-dir-name` | Validates that component directory name matches the `machineName` in component.yml. |
38
- | `component-exports` | Validates that component has a default export. |
39
49
  | `component-files` | Validates that component directory contains only allowed files. |
40
50
  | `component-imports` | Validates that component imports only from supported import sources and patterns. |
41
51
  | `component-no-hierarchy` | Validates that all component directories are at the same level with no nesting hierarchy. |
42
- | `component-prop-names` | Validates that component prop IDs match the camelCase version of their titles. |
43
52
 
44
53
  ## Development
45
54
 
package/dist/index.js CHANGED
@@ -1,20 +1,37 @@
1
+ import jsxA11y from 'eslint-plugin-jsx-a11y';
2
+ import react from 'eslint-plugin-react';
3
+ import reactHooks from 'eslint-plugin-react-hooks';
1
4
  import eslintPluginYml from 'eslint-plugin-yml';
2
5
  import { defineConfig, globalIgnores } from 'eslint/config';
6
+ import tseslint from 'typescript-eslint';
7
+ import js from '@eslint/js';
3
8
  import globals from 'globals';
4
9
  import { dirname, basename } from 'path';
5
10
  import { existsSync, readdirSync } from 'fs';
6
11
  import { camelCase } from 'lodash-es';
7
- import jsxA11y from 'eslint-plugin-jsx-a11y';
8
- import react from 'eslint-plugin-react';
9
- import reactHooks from 'eslint-plugin-react-hooks';
10
- import js from '@eslint/js';
11
12
 
12
- // src/configs/next.ts
13
+ // src/configs/recommended.ts
14
+ var JS_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
15
+ var NAMED_SUFFIX = ".component.yml";
16
+ function isComponentEntrypoint(context) {
17
+ if (!isInComponentDir(context)) {
18
+ return false;
19
+ }
20
+ const componentDir = dirname(context.filename);
21
+ const files = getFilesInDirectory(componentDir);
22
+ const namedMetadataFile = files.find((file) => file.endsWith(NAMED_SUFFIX));
23
+ const componentBaseName = namedMetadataFile ? namedMetadataFile.slice(0, -NAMED_SUFFIX.length) : "index";
24
+ return JS_EXTENSIONS.some(
25
+ (ext) => basename(context.filename) === componentBaseName + "." + ext
26
+ );
27
+ }
13
28
  function isInComponentDir(context) {
14
29
  try {
15
30
  const componentDir = dirname(context.filename);
16
31
  const files = getFilesInDirectory(componentDir);
17
- return files.includes("component.yml");
32
+ return files.filter(
33
+ (file) => basename(file) === "component.yml" || file.endsWith(NAMED_SUFFIX)
34
+ ).length > 0;
18
35
  } catch {
19
36
  return false;
20
37
  }
@@ -22,7 +39,7 @@ function isInComponentDir(context) {
22
39
  function isComponentYmlFile(context) {
23
40
  try {
24
41
  const fileName = basename(context.filename);
25
- return fileName === "component.yml";
42
+ return fileName === "component.yml" || fileName.endsWith(NAMED_SUFFIX);
26
43
  } catch {
27
44
  return false;
28
45
  }
@@ -47,11 +64,23 @@ function getYAMLStringValue(node) {
47
64
  }
48
65
 
49
66
  // src/rules/component-dir-name.ts
67
+ var NAMED_SUFFIX2 = ".component.yml";
68
+ function getExpectedMachineName(filename) {
69
+ const fileName = basename(filename);
70
+ if (fileName !== "component.yml" && fileName.endsWith(NAMED_SUFFIX2)) {
71
+ return {
72
+ name: fileName.slice(0, -NAMED_SUFFIX2.length),
73
+ source: `metadata filename "${fileName}"`
74
+ };
75
+ }
76
+ const dirName = basename(dirname(filename));
77
+ return { name: dirName, source: `directory name "${dirName}"` };
78
+ }
50
79
  var rule = {
51
80
  meta: {
52
81
  type: "problem",
53
82
  docs: {
54
- description: "Validates that component directory name matches the machineName in component.yml"
83
+ description: "Validates that the machineName in component metadata matches the component directory name (index-style) or filename prefix (named-style)"
55
84
  }
56
85
  },
57
86
  create(context) {
@@ -59,6 +88,7 @@ var rule = {
59
88
  return {};
60
89
  }
61
90
  let hasMachineName = false;
91
+ const { name: expectedName, source: expectedSource } = getExpectedMachineName(context.filename);
62
92
  return {
63
93
  YAMLPair(node) {
64
94
  const keyName = getYAMLStringValue(node.key);
@@ -74,22 +104,18 @@ var rule = {
74
104
  });
75
105
  return;
76
106
  }
77
- const componentDir = dirname(context.filename);
78
- const componentDirName = basename(componentDir);
79
- if (componentDirName !== machineName) {
107
+ if (expectedName !== machineName) {
80
108
  context.report({
81
109
  node: node.value,
82
- message: `Component directory name "${componentDirName}" does not match machineName "${machineName}" from component.yml.`
110
+ message: `${expectedSource[0].toUpperCase()}${expectedSource.slice(1)} does not match machineName "${machineName}".`
83
111
  });
84
112
  }
85
113
  },
86
114
  "Program:exit"() {
87
115
  if (!hasMachineName) {
88
- const componentDir = dirname(context.filename);
89
- const componentDirName = basename(componentDir);
90
116
  context.report({
91
117
  loc: { line: 1, column: 0 },
92
- message: `machineName key is missing. Its value should match the directory name: "${componentDirName}".`
118
+ message: `machineName key is missing. Its value should be "${expectedName}" based on ${expectedSource.toLowerCase()}.`
93
119
  });
94
120
  }
95
121
  }
@@ -107,10 +133,7 @@ var rule2 = {
107
133
  }
108
134
  },
109
135
  create(context) {
110
- if (!context.filename.endsWith(".jsx") && !context.filename.endsWith(".js")) {
111
- return {};
112
- }
113
- if (!isInComponentDir(context)) {
136
+ if (!isComponentEntrypoint(context)) {
114
137
  return {};
115
138
  }
116
139
  let hasDefaultExport = false;
@@ -130,6 +153,150 @@ var rule2 = {
130
153
  }
131
154
  };
132
155
  var component_exports_default = rule2;
156
+ function extractProps(propsNode) {
157
+ if (!propsNode.value || propsNode.value.type !== "YAMLMapping") {
158
+ return [];
159
+ }
160
+ const propsMapping = propsNode.value;
161
+ const propertiesPair = propsMapping.pairs.find(
162
+ (p) => getYAMLStringValue(p.key) === "properties"
163
+ );
164
+ if (!propertiesPair || !propertiesPair.value || propertiesPair.value.type !== "YAMLMapping") {
165
+ return [];
166
+ }
167
+ const propertiesMapping = propertiesPair.value;
168
+ const props = [];
169
+ for (const pair of propertiesMapping.pairs) {
170
+ const propId = getYAMLStringValue(pair.key);
171
+ if (!propId) continue;
172
+ if (!pair.value || pair.value.type !== "YAMLMapping") continue;
173
+ const propMapping = pair.value;
174
+ const titlePair = propMapping.pairs.find(
175
+ (p) => getYAMLStringValue(p.key) === "title"
176
+ );
177
+ let title = null;
178
+ if (titlePair) {
179
+ title = getYAMLStringValue(titlePair.value);
180
+ }
181
+ props.push({
182
+ id: propId,
183
+ title,
184
+ node: pair
185
+ });
186
+ }
187
+ return props;
188
+ }
189
+ var rule3 = {
190
+ meta: {
191
+ type: "problem",
192
+ docs: {
193
+ description: "Validates that component prop IDs match the camelCase version of their titles"
194
+ }
195
+ },
196
+ create(context) {
197
+ if (!isComponentYmlFile(context)) {
198
+ return {};
199
+ }
200
+ return {
201
+ YAMLPair(node) {
202
+ const keyName = getYAMLStringValue(node.key);
203
+ if (keyName !== "props") {
204
+ return;
205
+ }
206
+ const props = extractProps(node);
207
+ if (props.length === 0) {
208
+ return;
209
+ }
210
+ for (const prop of props) {
211
+ if (!prop.title) {
212
+ context.report({
213
+ node: prop.node,
214
+ message: `Prop "${prop.id}" is missing a title.`
215
+ });
216
+ continue;
217
+ }
218
+ const expectedId = camelCase(prop.title);
219
+ if (prop.id !== expectedId) {
220
+ context.report({
221
+ node: prop.node,
222
+ message: `Prop machine name "${prop.id}" should be the camelCase version of its title. Expected: "${expectedId}". https://drupal.org/i/3524675`
223
+ });
224
+ }
225
+ }
226
+ }
227
+ };
228
+ }
229
+ };
230
+ var component_prop_names_default = rule3;
231
+
232
+ // src/configs/required.ts
233
+ var required = defineConfig([
234
+ globalIgnores(["**/dist/**"]),
235
+ {
236
+ files: ["**/*.{ts,tsx,js,jsx}"],
237
+ languageOptions: {
238
+ parser: tseslint.parser,
239
+ ecmaVersion: 2020,
240
+ globals: globals.browser,
241
+ parserOptions: {
242
+ ecmaVersion: "latest",
243
+ ecmaFeatures: { jsx: true },
244
+ sourceType: "module"
245
+ }
246
+ },
247
+ settings: { react: { version: "19.0" } }
248
+ },
249
+ eslintPluginYml.configs["flat/base"],
250
+ {
251
+ plugins: {
252
+ "drupal-canvas": {
253
+ rules: {
254
+ "component-dir-name": component_dir_name_default,
255
+ "component-exports": component_exports_default,
256
+ "component-prop-names": component_prop_names_default
257
+ }
258
+ }
259
+ },
260
+ rules: {
261
+ "drupal-canvas/component-dir-name": "error",
262
+ "drupal-canvas/component-exports": "error",
263
+ "drupal-canvas/component-prop-names": "error"
264
+ }
265
+ }
266
+ ]);
267
+ var required_default = required;
268
+
269
+ // src/configs/recommended.ts
270
+ var recommended = defineConfig([
271
+ required_default,
272
+ {
273
+ files: ["**/*.{ts,tsx,js,jsx}"],
274
+ plugins: {
275
+ react,
276
+ "react-hooks": reactHooks,
277
+ "jsx-a11y": jsxA11y
278
+ },
279
+ settings: {
280
+ "jsx-a11y": {
281
+ components: {
282
+ Image: "img"
283
+ }
284
+ }
285
+ },
286
+ rules: {
287
+ ...js.configs.recommended.rules,
288
+ ...react.configs.recommended.rules,
289
+ ...react.configs["jsx-runtime"].rules,
290
+ ...reactHooks.configs.recommended.rules,
291
+ ...jsxA11y.flatConfigs.recommended.rules,
292
+ "react/jsx-no-target-blank": "off",
293
+ "react/prop-types": "off"
294
+ }
295
+ },
296
+ ...tseslint.configs.recommended,
297
+ ...eslintPluginYml.configs["flat/recommended"]
298
+ ]);
299
+ var recommended_default = recommended;
133
300
  var REQUIRED_FILES = ["component.yml", "index.jsx"];
134
301
  var ALLOWED_FILES = ["component.yml", "index.jsx", "index.css"];
135
302
  var IGNORED_FILES = [
@@ -139,12 +306,13 @@ var IGNORED_FILES = [
139
306
  function isFileAllowed(fileName, allowedFiles) {
140
307
  return allowedFiles.some((allowedFile) => allowedFile === fileName);
141
308
  }
142
- var rule3 = {
309
+ var rule4 = {
143
310
  meta: {
144
311
  type: "problem",
145
312
  docs: {
146
313
  description: "Validates that component directory contains only allowed files"
147
- }
314
+ },
315
+ deprecated: true
148
316
  },
149
317
  create(context) {
150
318
  if (!isComponentYmlFile(context)) {
@@ -177,7 +345,7 @@ var rule3 = {
177
345
  };
178
346
  }
179
347
  };
180
- var component_files_default = rule3;
348
+ var component_files_default = rule4;
181
349
 
182
350
  // src/rules/component-imports.ts
183
351
  function checkImportSource(context, node, source) {
@@ -308,13 +476,14 @@ function checkImportSource(context, node, source) {
308
476
  message: `Importing "${source}" is not supported. If this is a local import via a path alias, use the "@/components/" alias instead. If you are importing a third-party package, see the list of supported packages at https://project.pages.drupalcode.org/canvas/code-components/packages. (The status of supporting any third-party package can be tracked at https://drupal.org/i/3560197.)`
309
477
  });
310
478
  }
311
- var rule4 = {
479
+ var rule5 = {
312
480
  meta: {
313
481
  type: "problem",
314
482
  docs: {
315
483
  description: "Validates that component imports only from supported import sources and patterns"
316
484
  },
317
- fixable: "code"
485
+ fixable: "code",
486
+ deprecated: true
318
487
  },
319
488
  create(context) {
320
489
  if (!isInComponentDir(context)) {
@@ -334,7 +503,7 @@ var rule4 = {
334
503
  };
335
504
  }
336
505
  };
337
- var component_imports_default = rule4;
506
+ var component_imports_default = rule5;
338
507
  function findTopmostComponentsParentDir(currentParentDir, rootDir) {
339
508
  if (currentParentDir === rootDir) {
340
509
  return currentParentDir;
@@ -355,12 +524,13 @@ function hasComponentSubdirectories(dirPath) {
355
524
  }
356
525
  return false;
357
526
  }
358
- var rule5 = {
527
+ var rule6 = {
359
528
  meta: {
360
529
  type: "problem",
361
530
  docs: {
362
531
  description: "Validates that all component directories are at the same level with no nesting hierarchy"
363
- }
532
+ },
533
+ deprecated: true
364
534
  },
365
535
  create(context) {
366
536
  if (!isComponentYmlFile(context)) {
@@ -389,124 +559,9 @@ var rule5 = {
389
559
  };
390
560
  }
391
561
  };
392
- var component_no_hierarchy_default = rule5;
393
- function extractProps(propsNode) {
394
- if (!propsNode.value || propsNode.value.type !== "YAMLMapping") {
395
- return [];
396
- }
397
- const propsMapping = propsNode.value;
398
- const propertiesPair = propsMapping.pairs.find(
399
- (p) => getYAMLStringValue(p.key) === "properties"
400
- );
401
- if (!propertiesPair || !propertiesPair.value || propertiesPair.value.type !== "YAMLMapping") {
402
- return [];
403
- }
404
- const propertiesMapping = propertiesPair.value;
405
- const props = [];
406
- for (const pair of propertiesMapping.pairs) {
407
- const propId = getYAMLStringValue(pair.key);
408
- if (!propId) continue;
409
- if (!pair.value || pair.value.type !== "YAMLMapping") continue;
410
- const propMapping = pair.value;
411
- const titlePair = propMapping.pairs.find(
412
- (p) => getYAMLStringValue(p.key) === "title"
413
- );
414
- let title = null;
415
- if (titlePair) {
416
- title = getYAMLStringValue(titlePair.value);
417
- }
418
- props.push({
419
- id: propId,
420
- title,
421
- node: pair
422
- });
423
- }
424
- return props;
425
- }
426
- var rule6 = {
427
- meta: {
428
- type: "problem",
429
- docs: {
430
- description: "Validates that component prop IDs match the camelCase version of their titles"
431
- }
432
- },
433
- create(context) {
434
- if (!isComponentYmlFile(context)) {
435
- return {};
436
- }
437
- return {
438
- YAMLPair(node) {
439
- const keyName = getYAMLStringValue(node.key);
440
- if (keyName !== "props") {
441
- return;
442
- }
443
- const props = extractProps(node);
444
- if (props.length === 0) {
445
- return;
446
- }
447
- for (const prop of props) {
448
- if (!prop.title) {
449
- context.report({
450
- node: prop.node,
451
- message: `Prop "${prop.id}" is missing a title.`
452
- });
453
- continue;
454
- }
455
- const expectedId = camelCase(prop.title);
456
- if (prop.id !== expectedId) {
457
- context.report({
458
- node: prop.node,
459
- message: `Prop machine name "${prop.id}" should be the camelCase version of its title. Expected: "${expectedId}". https://drupal.org/i/3524675`
460
- });
461
- }
462
- }
463
- }
464
- };
465
- }
466
- };
467
- var component_prop_names_default = rule6;
562
+ var component_no_hierarchy_default = rule6;
468
563
 
469
- // src/configs/next.ts
470
- var required = defineConfig([
471
- globalIgnores(["**/dist/**"]),
472
- {
473
- files: ["**/*.{js,jsx}"],
474
- languageOptions: {
475
- ecmaVersion: 2020,
476
- globals: globals.browser,
477
- parserOptions: {
478
- ecmaVersion: "latest",
479
- ecmaFeatures: { jsx: true },
480
- sourceType: "module"
481
- }
482
- },
483
- settings: { react: { version: "19.0" } }
484
- },
485
- eslintPluginYml.configs["flat/base"],
486
- {
487
- plugins: {
488
- "drupal-canvas": {
489
- rules: {
490
- "component-dir-name": component_dir_name_default,
491
- "component-exports": component_exports_default,
492
- "component-files": component_files_default,
493
- "component-imports": component_imports_default,
494
- "component-no-hierarchy": component_no_hierarchy_default,
495
- "component-prop-names": component_prop_names_default
496
- }
497
- }
498
- },
499
- rules: {
500
- "drupal-canvas/component-dir-name": "error",
501
- // 'drupal-canvas/component-exports': 'error',
502
- // 'drupal-canvas/component-files': 'error',
503
- // 'drupal-canvas/component-imports': 'error',
504
- // 'drupal-canvas/component-no-hierarchy': 'error',
505
- "drupal-canvas/component-prop-names": "error"
506
- }
507
- }
508
- ]);
509
- var next_default = required;
564
+ // src/configs/requiredDeprecated.ts
510
565
  var required2 = defineConfig([
511
566
  globalIgnores(["**/dist/**"]),
512
567
  {
@@ -546,47 +601,17 @@ var required2 = defineConfig([
546
601
  }
547
602
  }
548
603
  ]);
549
- var required_default = required2;
550
-
551
- // src/configs/recommended.ts
552
- var recommended = defineConfig([
553
- required_default,
554
- {
555
- files: ["**/*.{js,jsx}"],
556
- plugins: {
557
- react,
558
- "react-hooks": reactHooks,
559
- "jsx-a11y": jsxA11y
560
- },
561
- settings: {
562
- "jsx-a11y": {
563
- components: {
564
- Image: "img"
565
- }
566
- }
567
- },
568
- rules: {
569
- ...js.configs.recommended.rules,
570
- ...react.configs.recommended.rules,
571
- ...react.configs["jsx-runtime"].rules,
572
- ...reactHooks.configs.recommended.rules,
573
- ...jsxA11y.flatConfigs.recommended.rules,
574
- "react/jsx-no-target-blank": "off",
575
- "react/prop-types": "off"
576
- }
577
- },
578
- ...eslintPluginYml.configs["flat/recommended"]
579
- ]);
580
- var recommended_default = recommended;
604
+ var requiredDeprecated_default = required2;
581
605
  var strict = defineConfig([
582
606
  recommended_default,
583
607
  {
584
- files: ["**/*.{js,jsx}"],
608
+ files: ["**/*.{ts,tsx,js,jsx}"],
585
609
  rules: {
586
610
  ...jsxA11y.flatConfigs.strict.rules
587
611
  }
588
- }
612
+ },
613
+ ...tseslint.configs.strict
589
614
  ]);
590
615
  var strict_default = strict;
591
616
 
592
- export { next_default as next, recommended_default as recommended, required_default as required, strict_default as strict };
617
+ export { recommended_default as recommended, required_default as required, requiredDeprecated_default as requiredDeprecated, strict_default as strict };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drupal-canvas/eslint-config",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "ESLint config for validating Drupal Canvas Code Components",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,7 +32,8 @@
32
32
  "eslint-plugin-react-hooks": "^7.0.1",
33
33
  "eslint-plugin-yml": "^1.19.0",
34
34
  "globals": "^16.5.0",
35
- "lodash-es": "^4.17.21"
35
+ "lodash-es": "^4.17.21",
36
+ "typescript-eslint": "^8.56.1"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/eslint-plugin-jsx-a11y": "^6.5.6",