diff --git a/COVERAGE.md b/COVERAGE.md
index 3a8ebc0..77bab8e 100644
--- a/COVERAGE.md
+++ b/COVERAGE.md
@@ -37,16 +37,16 @@ We currently cover the following components:
- [x] Input
- [x] Label
- [x] Link
- - [] Menu
- - [] Menu
- - [] MenuList
- - [] MessageBar
+ - [x] Menu
+ - [x] Menu
+ - [x] MenuList
+ - [x] MessageBar
- [N/A] Overflow
- [] Persona
- [] Popover
- [N/A] Portal
- [x] ProgressBar
- - [] Rating
+ - [x] Rating
- [] RatingDisplay
- [x] Radio
- [x] RadioGroup
diff --git a/docs/rules/rating-needs-name.md b/docs/rules/rating-needs-name.md
new file mode 100644
index 0000000..fcda35e
--- /dev/null
+++ b/docs/rules/rating-needs-name.md
@@ -0,0 +1,36 @@
+# Accessibility: Ratings must have accessible labelling: name, aria-label, aria-labelledby or itemLabel which generates aria-label (`@microsoft/fluentui-jsx-a11y/rating-needs-name`)
+
+All interactive elements must have an accessible name.
+
+## Rule Details
+
+This rule aims to enforce that a Rating element must have an accessible label associated with it.
+
+Examples of **incorrect** code for this rule:
+
+```js
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```js
+
+ `Rating of ${number} starts`} />
+
+```
+
+### Options
+
+FluentUI supports receiving a function that will add the aria-label to the element with the number. This prop is called itemLabel.
+If this is not the desired route, a name, aria-label or aria-labelledby can be added instead.
+
+## When Not To Use It
+
+You might want to turn this rule off if you don't intend for this component to be read by screen readers.
+
+## Further Reading
+
+- [ARIA in HTML](https://www.w3.org/TR/html-aria/)
diff --git a/lib/index.ts b/lib/index.ts
index d7ee438..89944eb 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -24,6 +24,7 @@ import tooltipNotRecommended from "./rules/tooltip-not-recommended";
import avatarNeedsName from "./rules/avatar-needs-name";
import radioButtonMissingLabel from "./rules/radio-button-missing-label";
import radiogroupMissingLabel from "./rules/radiogroup-missing-label";
+import ratingNeedsName from "./rules/rating-needs-name";
import dialogbodyNeedsTitleContentAndActions from "./rules/dialogbody-needs-title-content-and-actions";
import dialogsurfaceNeedsAria from "./rules/dialogsurface-needs-aria";
import spinnerNeedsLabelling from "./rules/spinner-needs-labelling";
@@ -62,6 +63,7 @@ module.exports = {
"avatar-needs-name": avatarNeedsName,
"radio-button-missing-label": radioButtonMissingLabel,
"radiogroup-missing-label": radiogroupMissingLabel,
+ "rating-needs-name": ratingNeedsName,
"prefer-aria-over-title-attribute": preferAriaOverTitleAttribute,
"dialogbody-needs-title-content-and-actions": dialogbodyNeedsTitleContentAndActions,
"dialogsurface-needs-aria": dialogsurfaceNeedsAria,
@@ -93,6 +95,7 @@ module.exports = {
"@microsoft/fluentui-jsx-a11y/avatar-needs-name": "error",
"@microsoft/fluentui-jsx-a11y/radio-button-missing-label": "error",
"@microsoft/fluentui-jsx-a11y/radiogroup-missing-label": "error",
+ "@microsoft/fluentui-jsx-a11y/rating-needs-name": "error",
"@microsoft/fluentui-jsx-a11y/prefer-aria-over-title-attribute": "warn",
"@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": "error",
"@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": "error",
diff --git a/lib/rules/rating-needs-name.ts b/lib/rules/rating-needs-name.ts
new file mode 100644
index 0000000..1592e33
--- /dev/null
+++ b/lib/rules/rating-needs-name.ts
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+"use strict";
+
+import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
+import { hasNonEmptyProp } from "../util/hasNonEmptyProp";
+import { elementType } from "jsx-ast-utils";
+import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils";
+import { JSXOpeningElement } from "estree-jsx";
+
+const rule = ESLintUtils.RuleCreator.withoutDocs({
+ defaultOptions: [],
+ meta: {
+ // possible error messages for the rule
+ messages: {
+ missingAriaLabel: 'Accessibility - ratings must have an accessible name or an itemLabel that generates an aria label'
+ },
+ // "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: Ratings must have accessible labelling: name, aria-label, aria-labelledby or itemLabel which generates aria-label",
+ recommended: "strict",
+ url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
+ },
+ schema: []
+ },
+
+ create(context) {
+ return {
+ // visitor functions for different types of nodes
+ JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
+ // if it is not a listed component, return
+ if (
+ elementType(node as JSXOpeningElement) !== "Rating"
+ ) {
+ return;
+ }
+
+ // wrapped in Label tag, labelled with htmlFor, labelled with aria-labelledby
+ if (
+ hasNonEmptyProp(node.attributes, "itemLabel") ||
+ hasNonEmptyProp(node.attributes, "name") ||
+ hasNonEmptyProp(node.attributes, "aria-label") ||
+ hasAssociatedLabelViaAriaLabelledBy(node, context)
+ ) {
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: `missingAriaLabel`
+ });
+ }
+ };
+ }
+});
+
+export default rule;
diff --git a/tests/lib/rules/rating-needs-name.test.ts b/tests/lib/rules/rating-needs-name.test.ts
new file mode 100644
index 0000000..abb58c3
--- /dev/null
+++ b/tests/lib/rules/rating-needs-name.test.ts
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+import { Rule } from "eslint";
+import ruleTester from "./helper/ruleTester";
+import rule from "../../../lib/rules/rating-needs-name";
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+ruleTester.run("rating-needs-name", rule as unknown as Rule.RuleModule, {
+ valid: [
+ // give me some code that won't trigger a warning
+ '',
+ '',
+ '',
+ '<>>',
+ '',
+ '',
+ '',
+ '<>>'
+ ],
+
+ invalid: [
+ {
+ code: "",
+ errors: [{ messageId: "missingAriaLabel" }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "missingAriaLabel" }]
+ }
+ ]
+});