eslint-plugin-primer-react 7.0.3-rc.fae9e4d → 7.1.0-rc.57f3453
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 +5 -1
- package/docs/rules/a11y-no-duplicate-form-labels.md +48 -0
- package/package-lock.json +76 -76
- package/package.json +2 -2
- package/src/configs/recommended.js +1 -0
- package/src/index.js +1 -0
- package/src/rules/__tests__/a11y-no-duplicate-form-labels.test.js +112 -0
- package/src/rules/a11y-no-duplicate-form-labels.js +69 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# eslint-plugin-primer-react
|
|
2
2
|
|
|
3
|
-
## 7.0
|
|
3
|
+
## 7.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#366](https://github.com/primer/eslint-plugin-primer-react/pull/366) [`c7bf278`](https://github.com/primer/eslint-plugin-primer-react/commit/c7bf278d7e7d6b389f325bfb9f57dca2114b3506) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - Add `a11y-no-duplicate-form-labels` rule to prevent duplicate labels on TextInput components.
|
|
4
8
|
|
|
5
9
|
### Patch Changes
|
|
6
10
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
## Rule Details
|
|
2
|
+
|
|
3
|
+
This rule prevents accessibility issues by ensuring form controls have only one label. When a `FormControl` contains both a `FormControl.Label` and a `TextInput` with an `aria-label`, it creates duplicate labels which can confuse screen readers and other assistive technologies.
|
|
4
|
+
|
|
5
|
+
👎 Examples of **incorrect** code for this rule:
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import {FormControl, TextInput} from '@primer/react'
|
|
9
|
+
|
|
10
|
+
function ExampleComponent() {
|
|
11
|
+
return (
|
|
12
|
+
// TextInput has aria-label when FormControl.Label is present
|
|
13
|
+
<FormControl>
|
|
14
|
+
<FormControl.Label>Form Input Label</FormControl.Label>
|
|
15
|
+
<TextInput aria-label="Form Input Label" />
|
|
16
|
+
</FormControl>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
👍 Examples of **correct** code for this rule:
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
import {FormControl, TextInput} from '@primer/react'
|
|
25
|
+
|
|
26
|
+
function ExampleComponent() {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{/* TextInput without aria-label when FormControl.Label is present */}
|
|
30
|
+
<FormControl>
|
|
31
|
+
<FormControl.Label>Form Input Label</FormControl.Label>
|
|
32
|
+
<TextInput />
|
|
33
|
+
</FormControl>
|
|
34
|
+
|
|
35
|
+
{/* TextInput with aria-label when no FormControl.Label is present */}
|
|
36
|
+
<FormControl>
|
|
37
|
+
<TextInput aria-label="Form Input Label" />
|
|
38
|
+
</FormControl>
|
|
39
|
+
|
|
40
|
+
{/* Using visuallyHidden FormControl.Label without aria-label */}
|
|
41
|
+
<FormControl>
|
|
42
|
+
<FormControl.Label visuallyHidden>Form Input Label</FormControl.Label>
|
|
43
|
+
<TextInput />
|
|
44
|
+
</FormControl>
|
|
45
|
+
</>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
```
|
package/package-lock.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "eslint-plugin-primer-react",
|
|
9
|
-
"version": "7.0.
|
|
9
|
+
"version": "7.0.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@styled-system/props": "^5.1.5",
|
|
13
|
-
"@typescript-eslint/utils": "8.
|
|
13
|
+
"@typescript-eslint/utils": "8.38.0",
|
|
14
14
|
"eslint-plugin-github": "^5.0.1",
|
|
15
15
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
16
16
|
"eslint-traverse": "^1.0.0",
|
|
@@ -2063,12 +2063,12 @@
|
|
|
2063
2063
|
}
|
|
2064
2064
|
},
|
|
2065
2065
|
"node_modules/@typescript-eslint/project-service": {
|
|
2066
|
-
"version": "8.
|
|
2067
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.
|
|
2068
|
-
"integrity": "sha512-
|
|
2066
|
+
"version": "8.38.0",
|
|
2067
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
|
|
2068
|
+
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
|
|
2069
2069
|
"dependencies": {
|
|
2070
|
-
"@typescript-eslint/tsconfig-utils": "^8.
|
|
2071
|
-
"@typescript-eslint/types": "^8.
|
|
2070
|
+
"@typescript-eslint/tsconfig-utils": "^8.38.0",
|
|
2071
|
+
"@typescript-eslint/types": "^8.38.0",
|
|
2072
2072
|
"debug": "^4.3.4"
|
|
2073
2073
|
},
|
|
2074
2074
|
"engines": {
|
|
@@ -2083,9 +2083,9 @@
|
|
|
2083
2083
|
}
|
|
2084
2084
|
},
|
|
2085
2085
|
"node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
|
|
2086
|
-
"version": "8.
|
|
2087
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.
|
|
2088
|
-
"integrity": "sha512-
|
|
2086
|
+
"version": "8.38.0",
|
|
2087
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
|
2088
|
+
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
|
|
2089
2089
|
"engines": {
|
|
2090
2090
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
2091
2091
|
},
|
|
@@ -2276,9 +2276,9 @@
|
|
|
2276
2276
|
}
|
|
2277
2277
|
},
|
|
2278
2278
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
|
2279
|
-
"version": "8.
|
|
2280
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.
|
|
2281
|
-
"integrity": "sha512-
|
|
2279
|
+
"version": "8.38.0",
|
|
2280
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
|
|
2281
|
+
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
|
|
2282
2282
|
"engines": {
|
|
2283
2283
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
2284
2284
|
},
|
|
@@ -2429,14 +2429,14 @@
|
|
|
2429
2429
|
}
|
|
2430
2430
|
},
|
|
2431
2431
|
"node_modules/@typescript-eslint/utils": {
|
|
2432
|
-
"version": "8.
|
|
2433
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.
|
|
2434
|
-
"integrity": "sha512-
|
|
2432
|
+
"version": "8.38.0",
|
|
2433
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
|
|
2434
|
+
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
|
|
2435
2435
|
"dependencies": {
|
|
2436
2436
|
"@eslint-community/eslint-utils": "^4.7.0",
|
|
2437
|
-
"@typescript-eslint/scope-manager": "8.
|
|
2438
|
-
"@typescript-eslint/types": "8.
|
|
2439
|
-
"@typescript-eslint/typescript-estree": "8.
|
|
2437
|
+
"@typescript-eslint/scope-manager": "8.38.0",
|
|
2438
|
+
"@typescript-eslint/types": "8.38.0",
|
|
2439
|
+
"@typescript-eslint/typescript-estree": "8.38.0"
|
|
2440
2440
|
},
|
|
2441
2441
|
"engines": {
|
|
2442
2442
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -2451,12 +2451,12 @@
|
|
|
2451
2451
|
}
|
|
2452
2452
|
},
|
|
2453
2453
|
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
|
|
2454
|
-
"version": "8.
|
|
2455
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.
|
|
2456
|
-
"integrity": "sha512
|
|
2454
|
+
"version": "8.38.0",
|
|
2455
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
|
|
2456
|
+
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
|
|
2457
2457
|
"dependencies": {
|
|
2458
|
-
"@typescript-eslint/types": "8.
|
|
2459
|
-
"@typescript-eslint/visitor-keys": "8.
|
|
2458
|
+
"@typescript-eslint/types": "8.38.0",
|
|
2459
|
+
"@typescript-eslint/visitor-keys": "8.38.0"
|
|
2460
2460
|
},
|
|
2461
2461
|
"engines": {
|
|
2462
2462
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -2467,9 +2467,9 @@
|
|
|
2467
2467
|
}
|
|
2468
2468
|
},
|
|
2469
2469
|
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
|
|
2470
|
-
"version": "8.
|
|
2471
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.
|
|
2472
|
-
"integrity": "sha512-
|
|
2470
|
+
"version": "8.38.0",
|
|
2471
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
|
2472
|
+
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
|
|
2473
2473
|
"engines": {
|
|
2474
2474
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
2475
2475
|
},
|
|
@@ -2479,14 +2479,14 @@
|
|
|
2479
2479
|
}
|
|
2480
2480
|
},
|
|
2481
2481
|
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
|
|
2482
|
-
"version": "8.
|
|
2483
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.
|
|
2484
|
-
"integrity": "sha512-
|
|
2485
|
-
"dependencies": {
|
|
2486
|
-
"@typescript-eslint/project-service": "8.
|
|
2487
|
-
"@typescript-eslint/tsconfig-utils": "8.
|
|
2488
|
-
"@typescript-eslint/types": "8.
|
|
2489
|
-
"@typescript-eslint/visitor-keys": "8.
|
|
2482
|
+
"version": "8.38.0",
|
|
2483
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
|
|
2484
|
+
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
|
|
2485
|
+
"dependencies": {
|
|
2486
|
+
"@typescript-eslint/project-service": "8.38.0",
|
|
2487
|
+
"@typescript-eslint/tsconfig-utils": "8.38.0",
|
|
2488
|
+
"@typescript-eslint/types": "8.38.0",
|
|
2489
|
+
"@typescript-eslint/visitor-keys": "8.38.0",
|
|
2490
2490
|
"debug": "^4.3.4",
|
|
2491
2491
|
"fast-glob": "^3.3.2",
|
|
2492
2492
|
"is-glob": "^4.0.3",
|
|
@@ -2506,11 +2506,11 @@
|
|
|
2506
2506
|
}
|
|
2507
2507
|
},
|
|
2508
2508
|
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
|
|
2509
|
-
"version": "8.
|
|
2510
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.
|
|
2511
|
-
"integrity": "sha512-
|
|
2509
|
+
"version": "8.38.0",
|
|
2510
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
|
|
2511
|
+
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
|
|
2512
2512
|
"dependencies": {
|
|
2513
|
-
"@typescript-eslint/types": "8.
|
|
2513
|
+
"@typescript-eslint/types": "8.38.0",
|
|
2514
2514
|
"eslint-visitor-keys": "^4.2.1"
|
|
2515
2515
|
},
|
|
2516
2516
|
"engines": {
|
|
@@ -10271,19 +10271,19 @@
|
|
|
10271
10271
|
}
|
|
10272
10272
|
},
|
|
10273
10273
|
"@typescript-eslint/project-service": {
|
|
10274
|
-
"version": "8.
|
|
10275
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.
|
|
10276
|
-
"integrity": "sha512-
|
|
10274
|
+
"version": "8.38.0",
|
|
10275
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
|
|
10276
|
+
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
|
|
10277
10277
|
"requires": {
|
|
10278
|
-
"@typescript-eslint/tsconfig-utils": "^8.
|
|
10279
|
-
"@typescript-eslint/types": "^8.
|
|
10278
|
+
"@typescript-eslint/tsconfig-utils": "^8.38.0",
|
|
10279
|
+
"@typescript-eslint/types": "^8.38.0",
|
|
10280
10280
|
"debug": "^4.3.4"
|
|
10281
10281
|
},
|
|
10282
10282
|
"dependencies": {
|
|
10283
10283
|
"@typescript-eslint/types": {
|
|
10284
|
-
"version": "8.
|
|
10285
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.
|
|
10286
|
-
"integrity": "sha512-
|
|
10284
|
+
"version": "8.38.0",
|
|
10285
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
|
10286
|
+
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="
|
|
10287
10287
|
}
|
|
10288
10288
|
}
|
|
10289
10289
|
},
|
|
@@ -10391,9 +10391,9 @@
|
|
|
10391
10391
|
}
|
|
10392
10392
|
},
|
|
10393
10393
|
"@typescript-eslint/tsconfig-utils": {
|
|
10394
|
-
"version": "8.
|
|
10395
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.
|
|
10396
|
-
"integrity": "sha512-
|
|
10394
|
+
"version": "8.38.0",
|
|
10395
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
|
|
10396
|
+
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
|
|
10397
10397
|
"requires": {}
|
|
10398
10398
|
},
|
|
10399
10399
|
"@typescript-eslint/type-utils": {
|
|
@@ -10475,39 +10475,39 @@
|
|
|
10475
10475
|
}
|
|
10476
10476
|
},
|
|
10477
10477
|
"@typescript-eslint/utils": {
|
|
10478
|
-
"version": "8.
|
|
10479
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.
|
|
10480
|
-
"integrity": "sha512-
|
|
10478
|
+
"version": "8.38.0",
|
|
10479
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
|
|
10480
|
+
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
|
|
10481
10481
|
"requires": {
|
|
10482
10482
|
"@eslint-community/eslint-utils": "^4.7.0",
|
|
10483
|
-
"@typescript-eslint/scope-manager": "8.
|
|
10484
|
-
"@typescript-eslint/types": "8.
|
|
10485
|
-
"@typescript-eslint/typescript-estree": "8.
|
|
10483
|
+
"@typescript-eslint/scope-manager": "8.38.0",
|
|
10484
|
+
"@typescript-eslint/types": "8.38.0",
|
|
10485
|
+
"@typescript-eslint/typescript-estree": "8.38.0"
|
|
10486
10486
|
},
|
|
10487
10487
|
"dependencies": {
|
|
10488
10488
|
"@typescript-eslint/scope-manager": {
|
|
10489
|
-
"version": "8.
|
|
10490
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.
|
|
10491
|
-
"integrity": "sha512
|
|
10489
|
+
"version": "8.38.0",
|
|
10490
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
|
|
10491
|
+
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
|
|
10492
10492
|
"requires": {
|
|
10493
|
-
"@typescript-eslint/types": "8.
|
|
10494
|
-
"@typescript-eslint/visitor-keys": "8.
|
|
10493
|
+
"@typescript-eslint/types": "8.38.0",
|
|
10494
|
+
"@typescript-eslint/visitor-keys": "8.38.0"
|
|
10495
10495
|
}
|
|
10496
10496
|
},
|
|
10497
10497
|
"@typescript-eslint/types": {
|
|
10498
|
-
"version": "8.
|
|
10499
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.
|
|
10500
|
-
"integrity": "sha512-
|
|
10498
|
+
"version": "8.38.0",
|
|
10499
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
|
10500
|
+
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="
|
|
10501
10501
|
},
|
|
10502
10502
|
"@typescript-eslint/typescript-estree": {
|
|
10503
|
-
"version": "8.
|
|
10504
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.
|
|
10505
|
-
"integrity": "sha512-
|
|
10503
|
+
"version": "8.38.0",
|
|
10504
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
|
|
10505
|
+
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
|
|
10506
10506
|
"requires": {
|
|
10507
|
-
"@typescript-eslint/project-service": "8.
|
|
10508
|
-
"@typescript-eslint/tsconfig-utils": "8.
|
|
10509
|
-
"@typescript-eslint/types": "8.
|
|
10510
|
-
"@typescript-eslint/visitor-keys": "8.
|
|
10507
|
+
"@typescript-eslint/project-service": "8.38.0",
|
|
10508
|
+
"@typescript-eslint/tsconfig-utils": "8.38.0",
|
|
10509
|
+
"@typescript-eslint/types": "8.38.0",
|
|
10510
|
+
"@typescript-eslint/visitor-keys": "8.38.0",
|
|
10511
10511
|
"debug": "^4.3.4",
|
|
10512
10512
|
"fast-glob": "^3.3.2",
|
|
10513
10513
|
"is-glob": "^4.0.3",
|
|
@@ -10517,11 +10517,11 @@
|
|
|
10517
10517
|
}
|
|
10518
10518
|
},
|
|
10519
10519
|
"@typescript-eslint/visitor-keys": {
|
|
10520
|
-
"version": "8.
|
|
10521
|
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.
|
|
10522
|
-
"integrity": "sha512-
|
|
10520
|
+
"version": "8.38.0",
|
|
10521
|
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
|
|
10522
|
+
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
|
|
10523
10523
|
"requires": {
|
|
10524
|
-
"@typescript-eslint/types": "8.
|
|
10524
|
+
"@typescript-eslint/types": "8.38.0",
|
|
10525
10525
|
"eslint-visitor-keys": "^4.2.1"
|
|
10526
10526
|
}
|
|
10527
10527
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "7.0
|
|
3
|
+
"version": "7.1.0-rc.57f3453",
|
|
4
4
|
"description": "ESLint rules for Primer React",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"eslint-traverse": "^1.0.0",
|
|
35
35
|
"lodash": "^4.17.21",
|
|
36
36
|
"styled-system": "^5.1.5",
|
|
37
|
-
"@typescript-eslint/utils": "8.
|
|
37
|
+
"@typescript-eslint/utils": "8.38.0",
|
|
38
38
|
"typescript": "^5.8.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
'primer-react/new-color-css-vars': 'error',
|
|
18
18
|
'primer-react/a11y-explicit-heading': 'error',
|
|
19
19
|
'primer-react/a11y-no-title-usage': 'error',
|
|
20
|
+
'primer-react/a11y-no-duplicate-form-labels': 'error',
|
|
20
21
|
'primer-react/no-deprecated-props': 'warn',
|
|
21
22
|
'primer-react/a11y-remove-disable-tooltip': 'error',
|
|
22
23
|
'primer-react/a11y-use-accessible-tooltip': 'error',
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ module.exports = {
|
|
|
12
12
|
'a11y-remove-disable-tooltip': require('./rules/a11y-remove-disable-tooltip'),
|
|
13
13
|
'a11y-use-accessible-tooltip': require('./rules/a11y-use-accessible-tooltip'),
|
|
14
14
|
'a11y-no-title-usage': require('./rules/a11y-no-title-usage'),
|
|
15
|
+
'a11y-no-duplicate-form-labels': require('./rules/a11y-no-duplicate-form-labels'),
|
|
15
16
|
'use-deprecated-from-deprecated': require('./rules/use-deprecated-from-deprecated'),
|
|
16
17
|
'no-wildcard-imports': require('./rules/no-wildcard-imports'),
|
|
17
18
|
'no-unnecessary-components': require('./rules/no-unnecessary-components'),
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const rule = require('../a11y-no-duplicate-form-labels')
|
|
2
|
+
const {RuleTester} = require('eslint')
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 'latest',
|
|
7
|
+
sourceType: 'module',
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
ruleTester.run('a11y-no-duplicate-form-labels', rule, {
|
|
15
|
+
valid: [
|
|
16
|
+
// TextInput without aria-label is valid
|
|
17
|
+
`import {FormControl, TextInput} from '@primer/react';
|
|
18
|
+
<FormControl>
|
|
19
|
+
<FormControl.Label>Form Input Label</FormControl.Label>
|
|
20
|
+
<TextInput />
|
|
21
|
+
</FormControl>`,
|
|
22
|
+
|
|
23
|
+
// TextInput with aria-label but no FormControl.Label is valid
|
|
24
|
+
`import {FormControl, TextInput} from '@primer/react';
|
|
25
|
+
<FormControl>
|
|
26
|
+
<TextInput aria-label="Form Input Label" />
|
|
27
|
+
</FormControl>`,
|
|
28
|
+
|
|
29
|
+
// TextInput with aria-label outside FormControl is valid
|
|
30
|
+
`import {TextInput} from '@primer/react';
|
|
31
|
+
<TextInput aria-label="Form Input Label" />`,
|
|
32
|
+
|
|
33
|
+
// TextInput with visuallyHidden FormControl.Label is valid
|
|
34
|
+
`import {FormControl, TextInput} from '@primer/react';
|
|
35
|
+
<FormControl>
|
|
36
|
+
<FormControl.Label visuallyHidden>Form Input Label</FormControl.Label>
|
|
37
|
+
<TextInput />
|
|
38
|
+
</FormControl>`,
|
|
39
|
+
|
|
40
|
+
// FormControl without FormControl.Label but with aria-label is valid
|
|
41
|
+
`import {FormControl, TextInput} from '@primer/react';
|
|
42
|
+
<FormControl>
|
|
43
|
+
<TextInput aria-label="Form Input Label" />
|
|
44
|
+
</FormControl>`,
|
|
45
|
+
|
|
46
|
+
// Multiple TextInputs with different approaches
|
|
47
|
+
`import {FormControl, TextInput} from '@primer/react';
|
|
48
|
+
<div>
|
|
49
|
+
<FormControl>
|
|
50
|
+
<FormControl.Label>Visible Label</FormControl.Label>
|
|
51
|
+
<TextInput />
|
|
52
|
+
</FormControl>
|
|
53
|
+
<FormControl>
|
|
54
|
+
<TextInput aria-label="Standalone Input" />
|
|
55
|
+
</FormControl>
|
|
56
|
+
</div>`,
|
|
57
|
+
],
|
|
58
|
+
invalid: [
|
|
59
|
+
{
|
|
60
|
+
code: `import {FormControl, TextInput} from '@primer/react';
|
|
61
|
+
<FormControl>
|
|
62
|
+
<FormControl.Label>Form Input Label</FormControl.Label>
|
|
63
|
+
<TextInput aria-label="Form Input Label" />
|
|
64
|
+
</FormControl>`,
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
messageId: 'duplicateLabel',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
code: `import {FormControl, TextInput} from '@primer/react';
|
|
73
|
+
<FormControl>
|
|
74
|
+
<FormControl.Label>Username</FormControl.Label>
|
|
75
|
+
<TextInput aria-label="Enter your username" />
|
|
76
|
+
</FormControl>`,
|
|
77
|
+
errors: [
|
|
78
|
+
{
|
|
79
|
+
messageId: 'duplicateLabel',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
code: `import {FormControl, TextInput} from '@primer/react';
|
|
85
|
+
<FormControl>
|
|
86
|
+
<FormControl.Label visuallyHidden>Password</FormControl.Label>
|
|
87
|
+
<TextInput aria-label="Enter password" />
|
|
88
|
+
</FormControl>`,
|
|
89
|
+
errors: [
|
|
90
|
+
{
|
|
91
|
+
messageId: 'duplicateLabel',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
code: `import {FormControl, TextInput} from '@primer/react';
|
|
97
|
+
<div>
|
|
98
|
+
<FormControl>
|
|
99
|
+
<FormControl.Label>Email</FormControl.Label>
|
|
100
|
+
<div>
|
|
101
|
+
<TextInput aria-label="Email address" />
|
|
102
|
+
</div>
|
|
103
|
+
</FormControl>
|
|
104
|
+
</div>`,
|
|
105
|
+
errors: [
|
|
106
|
+
{
|
|
107
|
+
messageId: 'duplicateLabel',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const {isPrimerComponent} = require('../utils/is-primer-component')
|
|
2
|
+
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
|
|
3
|
+
const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
|
|
4
|
+
|
|
5
|
+
const isFormControl = node => getJSXOpeningElementName(node) === 'FormControl'
|
|
6
|
+
const isFormControlLabel = node => getJSXOpeningElementName(node) === 'FormControl.Label'
|
|
7
|
+
const isTextInput = node => getJSXOpeningElementName(node) === 'TextInput'
|
|
8
|
+
|
|
9
|
+
const hasAriaLabel = node => {
|
|
10
|
+
const ariaLabel = getJSXOpeningElementAttribute(node, 'aria-label')
|
|
11
|
+
return !!ariaLabel
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const findFormControlLabel = (node, sourceCode) => {
|
|
15
|
+
// Traverse up the parent chain to find FormControl
|
|
16
|
+
let current = node.parent
|
|
17
|
+
while (current) {
|
|
18
|
+
if (
|
|
19
|
+
current.type === 'JSXElement' &&
|
|
20
|
+
isFormControl(current.openingElement) &&
|
|
21
|
+
isPrimerComponent(current.openingElement.name, sourceCode.getScope(current))
|
|
22
|
+
) {
|
|
23
|
+
// Found FormControl, now check if it has a FormControl.Label child
|
|
24
|
+
return current.children.some(
|
|
25
|
+
child =>
|
|
26
|
+
child.type === 'JSXElement' &&
|
|
27
|
+
isFormControlLabel(child.openingElement) &&
|
|
28
|
+
isPrimerComponent(child.openingElement.name, sourceCode.getScope(child)),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
current = current.parent
|
|
32
|
+
}
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
meta: {
|
|
38
|
+
type: 'problem',
|
|
39
|
+
docs: {
|
|
40
|
+
description:
|
|
41
|
+
'Prevent duplicate labels on form inputs by disallowing aria-label on TextInput when FormControl.Label is present.',
|
|
42
|
+
url: require('../url')(module),
|
|
43
|
+
},
|
|
44
|
+
schema: [],
|
|
45
|
+
messages: {
|
|
46
|
+
duplicateLabel:
|
|
47
|
+
'TextInput should not have aria-label when FormControl.Label is present. Use FormControl.Label with visuallyHidden prop if needed.',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
create(context) {
|
|
51
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
52
|
+
return {
|
|
53
|
+
JSXOpeningElement(jsxNode) {
|
|
54
|
+
if (isPrimerComponent(jsxNode.name, sourceCode.getScope(jsxNode)) && isTextInput(jsxNode)) {
|
|
55
|
+
// Check if TextInput has aria-label
|
|
56
|
+
if (hasAriaLabel(jsxNode)) {
|
|
57
|
+
// Check if there's a FormControl.Label in the parent FormControl
|
|
58
|
+
if (findFormControlLabel(jsxNode, sourceCode)) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: jsxNode,
|
|
61
|
+
messageId: 'duplicateLabel',
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|