diff --git a/COVERAGE.md b/COVERAGE.md
index 3a8ebc0..70e187c 100644
--- a/COVERAGE.md
+++ b/COVERAGE.md
@@ -30,7 +30,7 @@ We currently cover the following components:
- [N/A] Divider
- [] Drawer
- [X] Dropdown
- - [] Field
+ - [x] Field
- [N/A] FluentProvider
- [] Image
- [] InfoLabel
diff --git a/README.md b/README.md
index e71727f..d269b58 100644
--- a/README.md
+++ b/README.md
@@ -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. | ✅ | | 🔧 |
diff --git a/dist/lib/index.js b/dist/lib/index.js
index 1d25d3d..e1784d5 100644
--- a/dist/lib/index.js
+++ b/dist/lib/index.js
@@ -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
//------------------------------------------------------------------------------
@@ -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: {
@@ -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"
}
}
}
diff --git a/dist/lib/rules/field-needs-labelling.d.ts b/dist/lib/rules/field-needs-labelling.d.ts
new file mode 100644
index 0000000..01fa01d
--- /dev/null
+++ b/dist/lib/rules/field-needs-labelling.d.ts
@@ -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;
+};
diff --git a/dist/lib/rules/field-needs-labelling.js b/dist/lib/rules/field-needs-labelling.js
new file mode 100644
index 0000000..eb3af8e
--- /dev/null
+++ b/dist/lib/rules/field-needs-labelling.js
@@ -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`
+ });
+ }
+ };
+ }
+};
diff --git a/docs/rules/field-needs-labelling.md b/docs/rules/field-needs-labelling.md
new file mode 100644
index 0000000..37fb792
--- /dev/null
+++ b/docs/rules/field-needs-labelling.md
@@ -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.
+
+
+
+Field must have `label` prop and either `validationMessage` or `hint` prop.
+
+
+
+## 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
+
+
+
+```
+
+```jsx
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```jsx
+
+
+
+```
+
+```jsx
+
+
+
+```
diff --git a/lib/index.ts b/lib/index.ts
index d7ee438..3fcff30 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -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
//------------------------------------------------------------------------------
@@ -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: {
@@ -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"
}
}
}
@@ -107,3 +109,4 @@ module.exports = {
module.exports.processors = {
// add your processors here
};
+
diff --git a/lib/rules/field-needs-labelling.js b/lib/rules/field-needs-labelling.js
new file mode 100644
index 0000000..4db33e4
--- /dev/null
+++ b/lib/rules/field-needs-labelling.js
@@ -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`
+ });
+ }
+ };
+ }
+};
+
diff --git a/tests/lib/rules/field-needs-labelling.js b/tests/lib/rules/field-needs-labelling.js
new file mode 100644
index 0000000..56fb68e
--- /dev/null
+++ b/tests/lib/rules/field-needs-labelling.js
@@ -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: [
+ `
+
+ `,
+ `
+
+ `
+ ],
+ invalid: [
+ {
+ code: `
+
+ `,
+ errors: [{ messageId: "noUnlabelledField" }]
+ },
+ {
+ code: `
+
+ `,
+ errors: [{ messageId: "noUnlabelledField" }]
+ }
+ ]
+});
+