diff --git a/lib/applicableComponents/dropdownBasedComponents.ts b/lib/applicableComponents/dropdownBasedComponents.ts new file mode 100644 index 0000000..43c31de --- /dev/null +++ b/lib/applicableComponents/dropdownBasedComponents.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const dropdownBasedComponents = ["Dropdown"]; + +export { dropdownBasedComponents }; diff --git a/lib/rules/dropdown-needs-labelling.ts b/lib/rules/dropdown-needs-labelling.ts index 8141c82..8c9d555 100644 --- a/lib/rules/dropdown-needs-labelling.ts +++ b/lib/rules/dropdown-needs-labelling.ts @@ -6,6 +6,7 @@ import { elementType } from "jsx-ast-utils"; import { hasAssociatedLabelViaAriaLabelledBy, hasAssociatedLabelViaHtmlFor, isInsideLabelTag } from "../util/labelUtils"; import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; import { JSXOpeningElement } from "estree-jsx"; +import { dropdownBasedComponents } from "../applicableComponents/dropdownBasedComponents"; //------------------------------------------------------------------------------ // Rule Definition @@ -34,7 +35,7 @@ const rule = ESLintUtils.RuleCreator.withoutDocs({ // visitor functions for different types of nodes JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Dropdown, return - if (elementType(node as JSXOpeningElement) !== "Dropdown") { + if (!dropdownBasedComponents.includes(elementType(node as unknown as JSXOpeningElement))) { return; } diff --git a/lib/rules/visual-label-better-than-aria-suggestion.ts b/lib/rules/visual-label-better-than-aria-suggestion.ts index c2226ec..1a750dd 100644 --- a/lib/rules/visual-label-better-than-aria-suggestion.ts +++ b/lib/rules/visual-label-better-than-aria-suggestion.ts @@ -3,6 +3,7 @@ import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; import { applicableComponents } from "../applicableComponents/inputBasedComponents"; +import { dropdownBasedComponents } from "../applicableComponents/dropdownBasedComponents"; import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; import { elementType } from "jsx-ast-utils"; import { JSXOpeningElement } from "estree-jsx"; @@ -34,7 +35,7 @@ const rule = ESLintUtils.RuleCreator.withoutDocs({ // visitor functions for different types of nodes JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a listed component, return - if (!applicableComponents.includes(elementType(node as unknown as JSXOpeningElement))) { + if (![dropdownBasedComponents, ...applicableComponents].includes(elementType(node as unknown as JSXOpeningElement))) { return; } diff --git a/lib/util/hasLabelledChildImage.ts b/lib/util/hasLabelledChildImage.ts index 443941c..372b9b9 100644 --- a/lib/util/hasLabelledChildImage.ts +++ b/lib/util/hasLabelledChildImage.ts @@ -25,7 +25,6 @@ const isJSXIdentifierWithName = (name: TSESTree.JSXTagNameExpression, validNames * @returns boolean */ const hasLabelledChildImage = (node: TSESTree.JSXElement): boolean => { - console.log("node::", node); if (!node.children || node.children.length === 0) { return false; } @@ -33,10 +32,6 @@ const hasLabelledChildImage = (node: TSESTree.JSXElement): boolean => { return flattenChildren(node).some(child => { if (child.type === "JSXElement" && isJSXIdentifierWithName(child.openingElement.name, mergedImageComponents)) { const attributes = child.openingElement.attributes; - console.log("attributes::", attributes); - console.log("hasAccessibilityAttributes(attributes)", hasAccessibilityAttributes(attributes)); - console.log("!isImageHidden(attributes)", !isImageHidden(attributes)); - return !isImageHidden(attributes) && hasAccessibilityAttributes(attributes); } return false; @@ -68,8 +63,10 @@ const isImageHidden = (attributes: TSESTree.JSXOpeningElement["attributes"]): bo return true; } - // Check if the image has an `aria-label` attribute with a non-empty value + // Check if the image has an `aria-label` or `aria-labelledby` attribute with a non-empty value const ariaLabelProp = getProp(attributes as unknown as JSXOpeningElement["attributes"], "aria-label"); + const ariaLabelledbyProp = getProp(attributes as unknown as JSXOpeningElement["attributes"], "aria-labelledby"); + if (ariaLabelProp) { const ariaLabelValue = getPropValue(ariaLabelProp); if (ariaLabelValue) { @@ -77,6 +74,13 @@ const isImageHidden = (attributes: TSESTree.JSXOpeningElement["attributes"]): bo } } + if (ariaLabelledbyProp) { + const ariaLabelledbyValue = getPropValue(ariaLabelledbyProp); + if (ariaLabelledbyValue) { + return false; // If `aria-labelledby` is present and has a value, the image is not hidden + } + } + // Check if the image has an `alt` attribute and return true if the `alt` value is falsy const altProp = getProp(attributes as unknown as JSXOpeningElement["attributes"], "alt"); if (altProp) { @@ -84,7 +88,7 @@ const isImageHidden = (attributes: TSESTree.JSXOpeningElement["attributes"]): bo return !altValue; // Returns true if `altValue` is falsy (e.g., empty string, null, or undefined) } - return true; // If neither `alt` nor `aria-label` is present, consider the image hidden + return true; // If neither `alt`, `aria-label`, nor `aria-labelledby` is present, consider the image hidden }; export { hasLabelledChildImage, isImageHidden, hasAccessibilityAttributes, isJSXIdentifierWithName }; diff --git a/package.json b/package.json index 39b97f2..762edc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/eslint-plugin-fluentui-jsx-a11y", - "version": "3.0.0-alpha.1", + "version": "3.0.0-alpha.2", "description": "Static AST checker for accessibility rules on FluentUI JSX elements.", "keywords": [ "eslint",