Skip to content

Commit 8fe5076

Browse files
Merge branch 'main' into users/sidhshar/fix-label-detection-jsx-expression-ids
2 parents a309bf0 + d7dcddd commit 8fe5076

18 files changed

+797
-52
lines changed

.vscode/extensions.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
{ "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] }
1+
{
2+
"recommendations": [
3+
"dbaeumer.vscode-eslint",
4+
"firsttris.vscode-jest-runner",
5+
"esbenp.prettier-vscode",
6+
"rvest.vs-code-prettier-eslint",
7+
"DavidAnson.vscode-markdownlint",
8+
"streetsidesoftware.code-spell-checker-cspell-bundled-dictionaries"
9+
]
10+
}

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any addi
2222

2323
- Install [Visual Studio Code](https://code.visualstudio.com/).
2424

25+
- Install the [recommended plugins](#recommended-vs-code-extensions) for [Visual Studio Code](https://code.visualstudio.com/).
26+
2527
- Install [Node.js](https://nodejs.org/en/), with [nvm](https://github.com/nvm-sh/nvm). Please use Node version 16 and npm v 8.
2628

2729
- Install [jscodeshift](https://github.com/facebook/jscodeshift) globally.
@@ -49,6 +51,16 @@ or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any addi
4951

5052
- Make and submit changes following the [pull request submission workflow](#pull-requests)
5153

54+
## Recommended VS Code Extensions
55+
56+
To ensure a consistent and productive development environment, install the following extensions in Visual Studio Code:
57+
58+
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) — Linting for JavaScript/TypeScript.
59+
- [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) — Run or debug Jest tests from context menu.
60+
- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) — Code formatting using Prettier.
61+
- [Prettier ESLint](https://marketplace.visualstudio.com/items?itemName=rvest.vs-code-prettier-eslint) — Format code with Prettier and ESLint integration.
62+
- [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) — Linting and style checks for Markdown files.
63+
5264
## To create a new ESLint rule
5365

5466
If you want to create a new ESLint rule:

COVERAGE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ We currently cover the following components:
6464
- [x] SpinButton
6565
- [x] Spinner
6666
- [x] SwatchPicker
67-
- [] ColorSwatch
68-
- [] ImageSwatch
69-
- [] EmptySwatch
70-
- [] SwatchPickerRow
67+
- [x] ColorSwatch
68+
- [x] ImageSwatch
69+
- [x] EmptySwatch
70+
- [N/A] SwatchPickerRow
7171
- [x] Switch
7272
- [] SearchBox
7373
- [] Table

KNOWN_ISSUES.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Known Issues
2+
3+
## No object props deconstruction
4+
5+
We currently do not support object props deconstruction.
6+
7+
e.g.
8+
9+
```tsx
10+
const buttonProps = {
11+
icon: <Calendar />,
12+
aria-label: 'start date'
13+
};
14+
15+
<Button {...buttonProps} />
16+
17+
```
18+
19+
Unfortunately, these will not be picked up by our linter. However, we hope to support this soon 🚀
20+
21+
See [issue #149](https://github.com/microsoft/eslint-plugin-fluentui-jsx-a11y/issues/149) for details.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,17 @@ Any use of third-party trademarks or logos are subject to those third-party's po
116116
| [badge-needs-accessible-name](docs/rules/badge-needs-accessible-name.md) | | ✅ | | 🔧 |
117117
| [breadcrumb-needs-labelling](docs/rules/breadcrumb-needs-labelling.md) | All interactive elements must have an accessible name | ✅ | | |
118118
| [checkbox-needs-labelling](docs/rules/checkbox-needs-labelling.md) | Accessibility: Checkbox without label must have an accessible and visual label: aria-labelledby | ✅ | | |
119+
| [colorswatch-needs-labelling](docs/rules/colorswatch-needs-labelling.md) | Accessibility: ColorSwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc.. | ✅ | | |
119120
| [combobox-needs-labelling](docs/rules/combobox-needs-labelling.md) | All interactive elements must have an accessible name | ✅ | | |
120121
| [compound-button-needs-labelling](docs/rules/compound-button-needs-labelling.md) | Accessibility: Compound buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby | ✅ | | |
121122
| [counter-badge-needs-count](docs/rules/counter-badge-needs-count.md) | | ✅ | | 🔧 |
122123
| [dialogbody-needs-title-content-and-actions](docs/rules/dialogbody-needs-title-content-and-actions.md) | A DialogBody should have a header(DialogTitle), content(DialogContent), and footer(DialogActions) | ✅ | | |
123124
| [dialogsurface-needs-aria](docs/rules/dialogsurface-needs-aria.md) | DialogueSurface need accessible labelling: aria-describedby on DialogueSurface and aria-label or aria-labelledby(if DialogueTitle is missing) | ✅ | | |
124125
| [dropdown-needs-labelling](docs/rules/dropdown-needs-labelling.md) | Accessibility: Dropdown menu must have an id and it needs to be linked via htmlFor of a Label | ✅ | | |
126+
| [emptyswatch-needs-labelling](docs/rules/emptyswatch-needs-labelling.md) | Accessibility: EmptySwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc.. | ✅ | | |
125127
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have label | ✅ | | |
126128
| [image-button-missing-aria](docs/rules/image-button-missing-aria.md) | Accessibility: Image buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby | ✅ | | |
129+
| [imageswatch-needs-labelling](docs/rules/imageswatch-needs-labelling.md) | Accessibility: ImageSwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc.. | ✅ | | |
127130
| [input-components-require-accessible-name](docs/rules/input-components-require-accessible-name.md) | Accessibility: Input fields must have accessible labelling: aria-label, aria-labelledby or an associated label | ✅ | | |
128131
| [link-missing-labelling](docs/rules/link-missing-labelling.md) | Accessibility: Image links must have an accessible name. Add either text content, labelling to the image or labelling to the link itself. | ✅ | | 🔧 |
129132
| [menu-item-needs-labelling](docs/rules/menu-item-needs-labelling.md) | Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby | ✅ | | |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Accessibility: ColorSwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc. (`@microsoft/fluentui-jsx-a11y/colorswatch-needs-labelling`)
2+
3+
💼 This rule is enabled in the ✅ `recommended` config.
4+
5+
<!-- end auto-generated rule header -->
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Accessibility: EmptySwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc. (`@microsoft/fluentui-jsx-a11y/emptyswatch-needs-labelling`)
2+
3+
💼 This rule is enabled in the ✅ `recommended` config.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
All interactive elements must have an accessible name.
8+
9+
`EmptySwatch` without a supported label lacks an accessible name for assistive technology users.
10+
11+
## Allowed labelling strategies
12+
13+
-`aria-label` **on `EmptySwatch`**
14+
-`aria-labelledby` **on `EmptySwatch`**
15+
-`htmlFor`/`id` (native `<label htmlFor="…">``id="…"` on `EmptySwatch`)
16+
- ✅ Wrapping native `<label>…</label>`
17+
-`Tooltip` parent with `relationship="label"`
18+
- ✅ Text content child (e.g., `<EmptySwatch>None</EmptySwatch>`)
19+
-`Field` parent (not allowed for `EmptySwatch`)
20+
- ❌ Container-only label (e.g., only the surrounding `SwatchPicker` is labelled)
21+
22+
## Ways to fix
23+
24+
- Add `aria-label`/`aria-labelledby` to `EmptySwatch`.
25+
- Use `<label htmlFor="…">` + `id="…"` on `EmptySwatch`.
26+
- Wrap in a native `<label>` or `Tooltip (relationship="label")`.
27+
- Provide meaningful text **as the child** of `EmptySwatch`.
28+
29+
## Rule Details
30+
31+
This rule ensures `EmptySwatch` is labelled using **allowed** mechanisms.
32+
33+
### Examples of **incorrect** code
34+
35+
```jsx
36+
// No label
37+
<SwatchPicker>
38+
<EmptySwatch value="none" />
39+
</SwatchPicker>
40+
```
41+
42+
```jsx
43+
// Container-only label: EmptySwatch itself is unnamed
44+
<SwatchPicker aria-label="Color picker">
45+
<EmptySwatch value="none" />
46+
</SwatchPicker>
47+
```
48+
49+
```jsx
50+
// Not allowed: Field parent labelling
51+
<SwatchPicker>
52+
<Field label="No color">
53+
<EmptySwatch value="none" />
54+
</Field>
55+
</SwatchPicker>
56+
```
57+
58+
### Examples of **correct** code
59+
60+
```jsx
61+
// aria-label
62+
<SwatchPicker>
63+
<EmptySwatch value="none" aria-label="No color" />
64+
</SwatchPicker>
65+
```
66+
67+
```jsx
68+
// aria-labelledby
69+
<>
70+
<span id="empty-no-color">No color</span>
71+
<SwatchPicker>
72+
<EmptySwatch value="none" aria-labelledby="empty-no-color" />
73+
</SwatchPicker>
74+
</>
75+
```
76+
77+
```jsx
78+
// htmlFor/id
79+
<>
80+
<label htmlFor="empty-none">No color</label>
81+
<SwatchPicker>
82+
<EmptySwatch id="empty-none" value="none" />
83+
</SwatchPicker>
84+
</>
85+
```
86+
87+
```jsx
88+
// Wrapping native <label>
89+
<label>
90+
No color
91+
<EmptySwatch value="none" />
92+
</label>
93+
```
94+
95+
```jsx
96+
// Tooltip (acts as a label)
97+
<SwatchPicker>
98+
<Tooltip relationship="label" content="No color">
99+
<EmptySwatch value="none" />
100+
</Tooltip>
101+
</SwatchPicker>
102+
```
103+
104+
```jsx
105+
// Text content child
106+
<SwatchPicker>
107+
<EmptySwatch value="none">No color</EmptySwatch>
108+
</SwatchPicker>
109+
```
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Accessibility: ImageSwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc. (`@microsoft/fluentui-jsx-a11y/imageswatch-needs-labelling`)
2+
3+
💼 This rule is enabled in the ✅ `recommended` config.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
All interactive elements must have an accessible name.
8+
9+
`ImageSwatch` without a supported label lacks an accessible name for assistive technology users.
10+
11+
## Allowed labelling strategies
12+
13+
-`aria-label` **on `ImageSwatch`**
14+
-`aria-labelledby` **on `ImageSwatch`**
15+
- ✅ Wrapping native `<label>…</label>`
16+
-`Tooltip` parent with `relationship="label"` (e.g., from Fluent UI)
17+
-`Field` parent (not allowed for `ImageSwatch`)
18+
-`htmlFor`/`id` (not allowed for `ImageSwatch`)
19+
- ❌ Text content child (not allowed)
20+
- ❌ Container-only label (e.g., only the surrounding `SwatchPicker` is labelled)
21+
22+
## Ways to fix
23+
24+
- Add `aria-label` or `aria-labelledby` **directly** to `ImageSwatch`.
25+
- Wrap the swatch in a native `<label>…</label>` with descriptive text.
26+
- Wrap in a `Tooltip` with `relationship="label"` and meaningful `content`.
27+
28+
## Rule Details
29+
30+
This rule ensures `ImageSwatch` receives a name via **supported** mechanisms.
31+
32+
### Examples of **incorrect** code
33+
34+
```jsx
35+
// No label at all
36+
<SwatchPicker>
37+
<ImageSwatch src="/none.png" value="none" />
38+
</SwatchPicker>
39+
```
40+
41+
```jsx
42+
// Container-only label: ImageSwatch itself is unnamed
43+
<SwatchPicker aria-label="Color picker">
44+
<ImageSwatch src="/none.png" value="none" />
45+
</SwatchPicker>
46+
```
47+
48+
```jsx
49+
// Not allowed for ImageSwatch: htmlFor/id
50+
<>
51+
<label htmlFor="img-none">No color</label>
52+
<SwatchPicker>
53+
<ImageSwatch id="img-none" src="/none.png" value="none" />
54+
</SwatchPicker>
55+
</>
56+
```
57+
58+
```jsx
59+
// Not allowed: text content child
60+
<SwatchPicker>
61+
<ImageSwatch src="/none.png" value="none">No color</ImageSwatch>
62+
</SwatchPicker>
63+
```
64+
65+
```jsx
66+
// Not allowed: Field parent labelling
67+
<SwatchPicker>
68+
<Field label="No color">
69+
<ImageSwatch src="/none.png" value="none" />
70+
</Field>
71+
</SwatchPicker>
72+
```
73+
74+
### Examples of **correct** code
75+
76+
```jsx
77+
// aria-label
78+
<SwatchPicker>
79+
<ImageSwatch src="/none.png" value="none" aria-label="No color" />
80+
</SwatchPicker>
81+
```
82+
83+
```jsx
84+
// aria-labelledby
85+
<>
86+
<span id="img-no-color">No color</span>
87+
<SwatchPicker>
88+
<ImageSwatch src="/none.png" value="none" aria-labelledby="img-no-color" />
89+
</SwatchPicker>
90+
</>
91+
```
92+
93+
```jsx
94+
// Wrapping native <label>
95+
<label>
96+
No color
97+
<ImageSwatch src="/none.png" value="none" />
98+
</label>
99+
```
100+
101+
```jsx
102+
// Tooltip (acts as a label)
103+
<SwatchPicker>
104+
<Tooltip relationship="label" content="No color">
105+
<ImageSwatch src="/none.png" value="none" />
106+
</Tooltip>
107+
</SwatchPicker>
108+
```

lib/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@ module.exports = {
2323
"@microsoft/fluentui-jsx-a11y/badge-needs-accessible-name": "error",
2424
"@microsoft/fluentui-jsx-a11y/breadcrumb-needs-labelling": "error",
2525
"@microsoft/fluentui-jsx-a11y/checkbox-needs-labelling": "error",
26+
"@microsoft/fluentui-jsx-a11y/colorswatch-needs-labelling": "error",
2627
"@microsoft/fluentui-jsx-a11y/combobox-needs-labelling": "error",
2728
"@microsoft/fluentui-jsx-a11y/compound-button-needs-labelling": "error",
2829
"@microsoft/fluentui-jsx-a11y/counter-badge-needs-count": "error",
2930
"@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": "error",
3031
"@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": "error",
3132
"@microsoft/fluentui-jsx-a11y/dropdown-needs-labelling": "error",
33+
"@microsoft/fluentui-jsx-a11y/emptyswatch-needs-labelling": "error",
3234
"@microsoft/fluentui-jsx-a11y/field-needs-labelling": "error",
3335
"@microsoft/fluentui-jsx-a11y/image-button-missing-aria": "error",
36+
"@microsoft/fluentui-jsx-a11y/imageswatch-needs-labelling": "error",
3437
"@microsoft/fluentui-jsx-a11y/input-components-require-accessible-name": "error",
3538
"@microsoft/fluentui-jsx-a11y/link-missing-labelling": "error",
3639
"@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling": "error",
@@ -61,14 +64,17 @@ module.exports = {
6164
"badge-needs-accessible-name": rules.badgeNeedsAccessibleName,
6265
"breadcrumb-needs-labelling": rules.breadcrumbNeedsLabelling,
6366
"checkbox-needs-labelling": rules.checkboxNeedsLabelling,
67+
"colorswatch-needs-labelling": rules.colorSwatchNeedsLabelling,
6468
"combobox-needs-labelling": rules.comboboxNeedsLabelling,
6569
"compound-button-needs-labelling": rules.compoundButtonNeedsLabelling,
6670
"counter-badge-needs-count": rules.counterBadgeNeedsCount,
6771
"dialogbody-needs-title-content-and-actions": rules.dialogbodyNeedsTitleContentAndActions,
6872
"dialogsurface-needs-aria": rules.dialogsurfaceNeedsAria,
6973
"dropdown-needs-labelling": rules.dropdownNeedsLabelling,
74+
"emptyswatch-needs-labelling": rules.emptySwatchNeedsLabelling,
7075
"field-needs-labelling": rules.fieldNeedsLabelling,
7176
"image-button-missing-aria": rules.imageButtonMissingAria,
77+
"imageswatch-needs-labelling": rules.imageSwatchNeedsLabelling,
7278
"input-components-require-accessible-name": rules.inputComponentsRequireAccessibleName,
7379
"link-missing-labelling": rules.linkMissingLabelling,
7480
"menu-item-needs-labelling": rules.menuItemNeedsLabelling,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { ESLintUtils } from "@typescript-eslint/utils";
5+
import { makeLabeledControlRule } from "../util/ruleFactory";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
export default ESLintUtils.RuleCreator.withoutDocs(
12+
makeLabeledControlRule({
13+
component: "ColorSwatch",
14+
messageId: "noUnlabeledColorSwatch",
15+
description: "Accessibility: ColorSwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc..",
16+
labelProps: ["aria-label"],
17+
allowFieldParent: true,
18+
allowHtmlFor: true,
19+
allowLabelledBy: false,
20+
allowWrappingLabel: true,
21+
allowTooltipParent: true,
22+
allowDescribedBy: false,
23+
allowLabeledChild: false,
24+
allowTextContentChild: true
25+
})
26+
);

0 commit comments

Comments
 (0)