Skip to content

Commit

Permalink
Merge pull request #108 from Harsh-Modi278/user/harshmodi/FieldLintRule
Browse files Browse the repository at this point in the history
feat: Add lint rule for Field component
  • Loading branch information
aubreyquinn authored Sep 20, 2024
2 parents 351fd16 + f4cfee3 commit 00bd24a
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 6 deletions.
2 changes: 1 addition & 1 deletion COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ We currently cover the following components:
- [N/A] Divider
- [] Drawer
- [X] Dropdown
- [] Field
- [x] Field
- [N/A] FluentProvider
- [] Image
- [] InfoLabel
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ Any use of third-party trademarks or logos are subject to those third-party's po
| [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) | ✅ | | |
| [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) | ✅ | | |
| [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 | ✅ | | |
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have either label, validationMessage and hint attributes | ✅ | | |
| [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 | ✅ | | |
| [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 | ✅ | | |
| [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. | ✅ | | 🔧 |
Expand Down
7 changes: 5 additions & 2 deletions dist/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const dialogsurface_needs_aria_1 = __importDefault(require("./rules/dialogsurfac
const spinner_needs_labelling_1 = __importDefault(require("./rules/spinner-needs-labelling"));
const badge_needs_accessible_name_1 = __importDefault(require("./rules/badge-needs-accessible-name"));
const progressbar_needs_labelling_1 = __importDefault(require("./rules/progressbar-needs-labelling"));
const field_needs_labelling_1 = __importDefault(require("./rules/field-needs-labelling"));
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -68,7 +69,8 @@ module.exports = {
"dialogsurface-needs-aria": dialogsurface_needs_aria_1.default,
"spinner-needs-labelling": spinner_needs_labelling_1.default,
"badge-needs-accessible-name": badge_needs_accessible_name_1.default,
"progressbar-needs-labelling": progressbar_needs_labelling_1.default
"progressbar-needs-labelling": progressbar_needs_labelling_1.default,
"field-needs-labelling": field_needs_labelling_1.default
},
configs: {
recommended: {
Expand Down Expand Up @@ -98,7 +100,8 @@ module.exports = {
"@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": "error",
"@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": "error",
"@microsoft/fluentui-jsx-a11y/spinner-needs-labelling": "error",
"@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": "error"
"@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": "error",
"@microsoft/fluentui-jsx-a11y/field-needs-labelling": "error"
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions dist/lib/rules/field-needs-labelling.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export namespace meta {
namespace messages {
let noUnlabelledField: string;
}
let type: string;
namespace docs {
let description: string;
let recommended: boolean;
let url: string;
}
let schema: never[];
}
export function create(context: any): {
JSXOpeningElement(node: any): void;
};
46 changes: 46 additions & 0 deletions dist/lib/rules/field-needs-labelling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
"use strict";
const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
const elementType = require("jsx-ast-utils").elementType;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
// possible error messages for the rule
messages: {
noUnlabelledField: "Accessibility: Field must have either label, validationMessage and hint attributes"
},
// "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules
type: "problem",
// docs for the rule
docs: {
description: "Accessibility: Field must have either label, validationMessage and hint attributes",
recommended: true,
url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
},
schema: []
},
// create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree
create(context) {
return {
// visitor functions for different types of nodes
JSXOpeningElement(node) {
// if it is not a Spinner, return
if (elementType(node) !== "Field") {
return;
}
if (hasNonEmptyProp(node.attributes, "label", true) &&
(hasNonEmptyProp(node.attributes, "validationMessage", true) || hasNonEmptyProp(node.attributes, "hint", true))) {
return;
}
// if it has no visual labelling, report error
context.report({
node,
messageId: `noUnlabelledField`
});
}
};
}
};
61 changes: 61 additions & 0 deletions docs/rules/field-needs-labelling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Accessibility: Field must have either label, validationMessage and hint attributes (`@microsoft/fluentui-jsx-a11y/field-needs-labelling`)

💼 This rule is enabled in the ✅ `recommended` config.

<!-- end auto-generated rule header -->

Field must have `label` prop and either `validationMessage` or `hint` prop.

<https://www.w3.org/TR/html-aria/>

## Ways to fix

- Make sure that Field component has following props:
- `label`
- `validationMessage` or `hint`

## Rule Details

This rule aims to make Field component accessible.

Examples of **incorrect** code for this rule:

```jsx
<Field
label="Example field"
validationState="success"
>
<ProgressBar value={0.5} max={1} />
</Field>
```

```jsx
<Field
validationState="success"
hint="This is a hint."
>
<ProgressBar value={0.5} max={1} />
</Field>
```

Examples of **correct** code for this rule:

```jsx
<Field
label="Example field"
validationState="success"
validationMessage="This is a success message."
>
<ProgressBar value={0.5} max={1} />
</Field>
```

```jsx
<Field
label="Example field"
validationState="success"
hint="This is a hint."
>
<ProgressBar value={0.5} max={1} />
</Field>
```
9 changes: 6 additions & 3 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import dialogsurfaceNeedsAria from "./rules/dialogsurface-needs-aria";
import spinnerNeedsLabelling from "./rules/spinner-needs-labelling";
import badgeNeedsAccessibleName from "./rules/badge-needs-accessible-name";
import progressbarNeedsLabelling from "./rules/progressbar-needs-labelling";

import fieldNeedsLabelling from "./rules/field-needs-labelling";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -67,7 +67,8 @@ module.exports = {
"dialogsurface-needs-aria": dialogsurfaceNeedsAria,
"spinner-needs-labelling": spinnerNeedsLabelling,
"badge-needs-accessible-name": badgeNeedsAccessibleName,
"progressbar-needs-labelling": progressbarNeedsLabelling
"progressbar-needs-labelling": progressbarNeedsLabelling,
"field-needs-labelling": fieldNeedsLabelling
},
configs: {
recommended: {
Expand Down Expand Up @@ -97,7 +98,8 @@ module.exports = {
"@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": "error",
"@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": "error",
"@microsoft/fluentui-jsx-a11y/spinner-needs-labelling": "error",
"@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": "error"
"@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": "error",
"@microsoft/fluentui-jsx-a11y/field-needs-labelling": "error"
}
}
}
Expand All @@ -107,3 +109,4 @@ module.exports = {
module.exports.processors = {
// add your processors here
};

55 changes: 55 additions & 0 deletions lib/rules/field-needs-labelling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
const elementType = require("jsx-ast-utils").elementType;

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
// possible error messages for the rule
messages: {
noUnlabelledField: "Accessibility: Field must have either label, validationMessage and hint attributes"
},
// "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules
type: "problem",
// docs for the rule
docs: {
description: "Accessibility: Field must have either label, validationMessage and hint attributes",
recommended: true,
url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
},
schema: []
},
// create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree
create(context) {
return {
// visitor functions for different types of nodes
JSXOpeningElement(node) {
// if it is not a Spinner, return
if (elementType(node) !== "Field") {
return;
}

if (
hasNonEmptyProp(node.attributes, "label", true) &&
(hasNonEmptyProp(node.attributes, "validationMessage", true) || hasNonEmptyProp(node.attributes, "hint", true))
) {
return;
}

// if it has no visual labelling, report error
context.report({
node,
messageId: `noUnlabelledField`
});
}
};
}
};

56 changes: 56 additions & 0 deletions tests/lib/rules/field-needs-labelling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/field-needs-labelling"),
RuleTester = require("eslint").RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester();
ruleTester.run("field-needs-labelling", rule, {
valid: [
`<Field
label="Example field"
validationState="success"
validationMessage="This is a success message."
>
<ProgressBar value={0.5} max={1} />
</Field>`,
`<Field
label="Example field"
validationState="success"
hint="This is a hint."
>
<ProgressBar value={0.5} max={1} />
</Field>`
],
invalid: [
{
code: `<Field
label="Example field"
validationState="success"
>
<ProgressBar value={0.5} max={1} />
</Field>`,
errors: [{ messageId: "noUnlabelledField" }]
},
{
code: `<Field
validationState="success"
hint="This is a hint."
>
<ProgressBar value={0.5} max={1} />
</Field>`,
errors: [{ messageId: "noUnlabelledField" }]
}
]
});

0 comments on commit 00bd24a

Please sign in to comment.