From 832cb01dde802e23b3aaa51b23680ac42eb1b459 Mon Sep 17 00:00:00 2001 From: Harsh-Modi278 Date: Thu, 19 Sep 2024 10:48:22 +0530 Subject: [PATCH 1/3] add rule for Field --- lib/rules/field-needs-labelling.js | 55 +++++++++++++++++++++++ tests/lib/rules/field-needs-labelling.js | 56 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 lib/rules/field-needs-labelling.js create mode 100644 tests/lib/rules/field-needs-labelling.js 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..ce36bf7 --- /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" }] + } + ] +}); + From fc5981c190c5d576858d0604ad903abd6c64d867 Mon Sep 17 00:00:00 2001 From: Harsh-Modi278 Date: Thu, 19 Sep 2024 11:02:57 +0530 Subject: [PATCH 2/3] add doc --- COVERAGE.md | 2 +- docs/rules/field-needs-labelling.md | 61 ++++++++++++++++++++++++ tests/lib/rules/field-needs-labelling.js | 12 ++--- 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 docs/rules/field-needs-labelling.md diff --git a/COVERAGE.md b/COVERAGE.md index f5c98d4..edf2c2b 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/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/tests/lib/rules/field-needs-labelling.js b/tests/lib/rules/field-needs-labelling.js index ce36bf7..56fb68e 100644 --- a/tests/lib/rules/field-needs-labelling.js +++ b/tests/lib/rules/field-needs-labelling.js @@ -18,16 +18,16 @@ const ruleTester = new RuleTester(); ruleTester.run("field-needs-labelling", rule, { valid: [ ` `, ` ` From b42e2f0feb28413aed42156d93381eb887df8a36 Mon Sep 17 00:00:00 2001 From: Harsh-Modi278 Date: Thu, 19 Sep 2024 23:45:41 +0530 Subject: [PATCH 3/3] address comments --- README.md | 1 + dist/lib/index.js | 7 +++- dist/lib/rules/field-needs-labelling.d.ts | 15 ++++++++ dist/lib/rules/field-needs-labelling.js | 46 +++++++++++++++++++++++ lib/index.ts | 8 +++- 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 dist/lib/rules/field-needs-labelling.d.ts create mode 100644 dist/lib/rules/field-needs-labelling.js 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 1649dd9..f2493d3 100644 --- a/dist/lib/index.js +++ b/dist/lib/index.js @@ -7,6 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); console.log("Loading my-eslint-plugin"); const prefer_aria_over_title_attribute_1 = __importDefault(require("./rules/prefer-aria-over-title-attribute")); +const field_needs_labelling_1 = __importDefault(require("./rules/field-needs-labelling")); //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ @@ -42,7 +43,8 @@ module.exports = { "dialogsurface-needs-aria": require("./rules/dialogsurface-needs-aria"), "spinner-needs-labelling": require("./rules/spinner-needs-labelling"), "badge-needs-accessible-name": require("./rules/badge-needs-accessible-name"), - "progressbar-needs-labelling": require("./rules/progressbar-needs-labelling") + "progressbar-needs-labelling": require("./rules/progressbar-needs-labelling"), + "field-needs-labelling": field_needs_labelling_1.default }, configs: { recommended: { @@ -72,7 +74,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/lib/index.ts b/lib/index.ts index 3e4f139..6266604 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,6 +3,7 @@ console.log("Loading my-eslint-plugin"); import preferAriaOverTitleAttribute from "./rules/prefer-aria-over-title-attribute"; +import fieldNeedsLabelling from "./rules/field-needs-labelling"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ @@ -40,7 +41,8 @@ module.exports = { "dialogsurface-needs-aria": require("./rules/dialogsurface-needs-aria"), "spinner-needs-labelling": require("./rules/spinner-needs-labelling"), "badge-needs-accessible-name": require("./rules/badge-needs-accessible-name"), - "progressbar-needs-labelling": require("./rules/progressbar-needs-labelling") + "progressbar-needs-labelling": require("./rules/progressbar-needs-labelling"), + "field-needs-labelling": fieldNeedsLabelling }, configs: { recommended: { @@ -70,7 +72,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" } } } @@ -80,3 +83,4 @@ module.exports = { module.exports.processors = { // add your processors here }; +