From de29ead6f38d3c4c236d11afdcfd8e23d7ec488e Mon Sep 17 00:00:00 2001 From: Ajay Agarwal Date: Fri, 20 Sep 2024 15:05:20 +0530 Subject: [PATCH 1/3] port existing rules and test to ts --- lib/index.ts | 79 ++++++++++----- ...js => accordion-header-needs-labelling.ts} | 36 +++---- ... accordion-item-needs-header-and-panel.ts} | 34 +++++-- ...tar-needs-name.js => avatar-needs-name.ts} | 23 +++-- ...aria-describedby-for-primary-labelling.ts} | 41 ++++---- ...name.js => badge-needs-accessible-name.ts} | 39 ++++---- ...lling.js => breadcrumb-needs-labelling.ts} | 22 ++--- ....js => compound-button-needs-labelling.ts} | 27 +++--- ...g-aria.js => image-button-missing-aria.ts} | 32 ++++--- ...o-empty-buttons.js => no-empty-buttons.ts} | 31 +++--- ...belling.js => checkbox-needs-labelling.ts} | 25 ++--- ...belling.js => combobox-needs-labelling.ts} | 24 ++--- ...logbody-needs-title-content-and-actions.js | 50 ---------- ...logbody-needs-title-content-and-actions.ts | 71 ++++++++++++++ lib/rules/dialogsurface-needs-aria.js | 76 --------------- lib/rules/dialogsurface-needs-aria.ts | 95 +++++++++++++++++++ ...belling.js => dropdown-needs-labelling.ts} | 24 ++--- ...put-components-require-accessible-name.ts} | 26 ++--- ...labelling.js => link-missing-labelling.ts} | 30 +++--- ...elling.js => menu-item-needs-labelling.ts} | 28 +++--- ...y-components.js => no-empty-components.ts} | 22 ++--- ...ling.js => progressbar-needs-labelling.ts} | 22 +++-- ...label.js => radio-button-missing-label.ts} | 25 ++--- ...g-label.js => radiogroup-missing-label.ts} | 25 ++--- ...ling.js => spin-button-needs-labelling.ts} | 23 +++-- ...=> spin-button-unrecommended-labelling.ts} | 22 +++-- ...abelling.js => spinner-needs-labelling.ts} | 27 +++--- ...labelling.js => switch-needs-labelling.ts} | 25 ++--- ...issing-aria.js => toolbar-missing-aria.ts} | 23 ++--- ...ommended.js => tooltip-not-recommended.ts} | 25 ++--- ... accordion-header-needs-labelling.test.ts} | 20 +--- ...rdion-item-needs-header-and-panel.test.ts} | 19 +--- ...eeds-name.js => avatar-needs-name.test.ts} | 20 +--- ...describedby-for-primary-labelling.test.ts} | 19 +--- ...js => badge-needs-accessible-name.test.ts} | 20 +--- ....js => breadcrumb-needs-labelling.test.ts} | 19 +--- ...> compound-button-needs-labelling.test.ts} | 11 +-- ...a.js => image-button-missing-aria.test.ts} | 10 +- ...ty-buttons.js => no-empty-buttons.test.ts} | 20 +--- ...ng.js => checkbox-needs-labelling.test.ts} | 20 +--- ...ng.js => combobox-needs-labelling.test.ts} | 19 +--- ...y-needs-title-content-and-actions.test.ts} | 31 ++---- ...ia.js => dialogsurface-needs-aria.test.ts} | 26 ++--- ...ng.js => dropdown-needs-labelling.test.ts} | 20 +--- tests/lib/rules/helper/ruleTester.ts | 7 ++ ...omponents-require-accessible-name.test.ts} | 25 ++--- ...abel-utils.test.js => label-utils.test.ts} | 7 +- ...ling.js => link-missing-labelling.test.ts} | 27 ++---- ...g.js => menu-item-needs-labelling.test.ts} | 19 +--- ...ponents.js => no-empty-components.test.ts} | 19 +--- .../prefer-aria-over-title-attribute.test.ts | 6 +- ...js => progressbar-needs-labelling.test.ts} | 11 +-- ....js => radio-button-missing-label.test.ts} | 20 +--- ...el.js => radioGroup-missing-label.test.ts} | 20 +--- ...js => spin-button-needs-labelling.test.ts} | 10 +- ...in-button-unrecommended-labelling.test.ts} | 10 +- ...ing.js => spinner-needs-labelling.test.ts} | 15 ++- ...ling.js => switch-needs-labelling.test.ts} | 10 +- ...g-aria.js => toolbar-missing-aria.test.ts} | 10 +- ...ded.js => tooltip-not-recommended.test.ts} | 19 +--- 60 files changed, 726 insertions(+), 835 deletions(-) rename lib/rules/{accordion-header-needs-labelling.js => accordion-header-needs-labelling.ts} (70%) rename lib/rules/{accordion-item-needs-header-and-panel.js => accordion-item-needs-header-and-panel.ts} (51%) rename lib/rules/{avatar-needs-name.js => avatar-needs-name.ts} (76%) rename lib/rules/{avoid-using-aria-describedby-for-primary-labelling.js => avoid-using-aria-describedby-for-primary-labelling.ts} (73%) rename lib/rules/{badge-needs-accessible-name.js => badge-needs-accessible-name.ts} (76%) rename lib/rules/{breadcrumb-needs-labelling.js => breadcrumb-needs-labelling.ts} (76%) rename lib/rules/buttons/{compound-button-needs-labelling.js => compound-button-needs-labelling.ts} (76%) rename lib/rules/buttons/{image-button-missing-aria.js => image-button-missing-aria.ts} (74%) rename lib/rules/buttons/{no-empty-buttons.js => no-empty-buttons.ts} (72%) rename lib/rules/{checkbox-needs-labelling.js => checkbox-needs-labelling.ts} (75%) rename lib/rules/{combobox-needs-labelling.js => combobox-needs-labelling.ts} (75%) delete mode 100644 lib/rules/dialogbody-needs-title-content-and-actions.js create mode 100644 lib/rules/dialogbody-needs-title-content-and-actions.ts delete mode 100644 lib/rules/dialogsurface-needs-aria.js create mode 100644 lib/rules/dialogsurface-needs-aria.ts rename lib/rules/{dropdown-needs-labelling.js => dropdown-needs-labelling.ts} (77%) rename lib/rules/{input-components-require-accessible-name.js => input-components-require-accessible-name.ts} (74%) rename lib/rules/{link-missing-labelling.js => link-missing-labelling.ts} (81%) rename lib/rules/{menu-item-needs-labelling.js => menu-item-needs-labelling.ts} (74%) rename lib/rules/{no-empty-components.js => no-empty-components.ts} (81%) rename lib/rules/{progressbar-needs-labelling.js => progressbar-needs-labelling.ts} (84%) rename lib/rules/{radio-button-missing-label.js => radio-button-missing-label.ts} (76%) rename lib/rules/{radiogroup-missing-label.js => radiogroup-missing-label.ts} (75%) rename lib/rules/{spin-button-needs-labelling.js => spin-button-needs-labelling.ts} (75%) rename lib/rules/{spin-button-unrecommended-labelling.js => spin-button-unrecommended-labelling.ts} (75%) rename lib/rules/{spinner-needs-labelling.js => spinner-needs-labelling.ts} (67%) rename lib/rules/{switch-needs-labelling.js => switch-needs-labelling.ts} (74%) rename lib/rules/{toolbar-missing-aria.js => toolbar-missing-aria.ts} (73%) rename lib/rules/{tooltip-not-recommended.js => tooltip-not-recommended.ts} (78%) rename tests/lib/rules/{accordion-header-needs-labelling.js => accordion-header-needs-labelling.test.ts} (86%) rename tests/lib/rules/{accordion-item-needs-header-and-panel.js => accordion-item-needs-header-and-panel.test.ts} (81%) rename tests/lib/rules/{avatar-needs-name.js => avatar-needs-name.test.ts} (82%) rename tests/lib/rules/{avoid-using-aria-describedby-for-primary-labelling.js => avoid-using-aria-describedby-for-primary-labelling.test.ts} (83%) rename tests/lib/rules/{badge-needs-accessible-name.js => badge-needs-accessible-name.test.ts} (85%) rename tests/lib/rules/{breadcrumb-needs-labelling.js => breadcrumb-needs-labelling.test.ts} (78%) rename tests/lib/rules/buttons/{compound-button-needs-labelling.js => compound-button-needs-labelling.test.ts} (88%) rename tests/lib/rules/buttons/{image-button-missing-aria.js => image-button-missing-aria.test.ts} (91%) rename tests/lib/rules/buttons/{no-empty-buttons.js => no-empty-buttons.test.ts} (91%) rename tests/lib/rules/{checkbox-needs-labelling.js => checkbox-needs-labelling.test.ts} (86%) rename tests/lib/rules/{combobox-needs-labelling.js => combobox-needs-labelling.test.ts} (87%) rename tests/lib/rules/{dialogbody-needs-title-content-and-actions.js => dialogbody-needs-title-content-and-actions.test.ts} (78%) rename tests/lib/rules/{dialogsurface-needs-aria.js => dialogsurface-needs-aria.test.ts} (88%) rename tests/lib/rules/{dropdown-needs-labelling.js => dropdown-needs-labelling.test.ts} (89%) create mode 100644 tests/lib/rules/helper/ruleTester.ts rename tests/lib/rules/{input-components-require-accessible-name.js => input-components-require-accessible-name.test.ts} (82%) rename tests/lib/rules/{label-utils.test.js => label-utils.test.ts} (91%) rename tests/lib/rules/{link-missing-labelling.js => link-missing-labelling.test.ts} (87%) rename tests/lib/rules/{menu-item-needs-labelling.js => menu-item-needs-labelling.test.ts} (82%) rename tests/lib/rules/{no-empty-components.js => no-empty-components.test.ts} (90%) rename tests/lib/rules/{progressbar-needs-labelling.js => progressbar-needs-labelling.test.ts} (94%) rename tests/lib/rules/{radio-button-missing-label.js => radio-button-missing-label.test.ts} (85%) rename tests/lib/rules/{radioGroup-missing-label.js => radioGroup-missing-label.test.ts} (86%) rename tests/lib/rules/{spin-button-needs-labelling.js => spin-button-needs-labelling.test.ts} (91%) rename tests/lib/rules/{spin-button-unrecommended-labelling.js => spin-button-unrecommended-labelling.test.ts} (78%) rename tests/lib/rules/{spinner-needs-labelling.js => spinner-needs-labelling.test.ts} (82%) rename tests/lib/rules/{switch-needs-labelling.js => switch-needs-labelling.test.ts} (90%) rename tests/lib/rules/{toolbar-missing-aria.js => toolbar-missing-aria.test.ts} (89%) rename tests/lib/rules/{tooltip-not-recommended.js => tooltip-not-recommended.test.ts} (74%) diff --git a/lib/index.ts b/lib/index.ts index 3e4f139..d7ee438 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,6 +3,33 @@ console.log("Loading my-eslint-plugin"); import preferAriaOverTitleAttribute from "./rules/prefer-aria-over-title-attribute"; +import checkboxNeedsLabelling from "./rules/checkbox-needs-labelling"; +import imageButtonMissingAria from "./rules/buttons/image-button-missing-aria"; +import linkMissingLabelling from "./rules/link-missing-labelling"; +import inputComponentsRequireAccessibleName from "./rules/input-components-require-accessible-name"; +import menuItemNeedsLabelling from "./rules/menu-item-needs-labelling"; +import switchNeedsLabelling from "./rules/switch-needs-labelling"; +import toolbarMissingAria from "./rules/toolbar-missing-aria"; +import comboboxNeedsLabelling from "./rules/combobox-needs-labelling"; +import noEmptyComponents from "./rules/no-empty-components"; +import accordionHeaderNeedsLabelling from "./rules/accordion-header-needs-labelling"; +import accordionItemNeedsHeaderAndPanel from "./rules/accordion-item-needs-header-and-panel"; +import compoundButtonNeedsLabelling from "./rules/buttons/compound-button-needs-labelling"; +import noEmptyButtons from "./rules/buttons/no-empty-buttons"; +import spinButtonNeedsLabelling from "./rules/spin-button-needs-labelling"; +import spinButtonUnrecommendedLabelling from "./rules/spin-button-unrecommended-labelling"; +import breadcrumbNeedsLabelling from "./rules/breadcrumb-needs-labelling"; +import dropwdonNeedsLabelling from "./rules/dropdown-needs-labelling"; +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 dialogbodyNeedsTitleContentAndActions from "./rules/dialogbody-needs-title-content-and-actions"; +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"; + //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ @@ -14,33 +41,33 @@ import preferAriaOverTitleAttribute from "./rules/prefer-aria-over-title-attribu // import all rules in lib/rules module.exports = { rules: { - "checkbox-needs-labelling": require("./rules/checkbox-needs-labelling"), - "image-button-missing-aria": require("./rules/buttons/image-button-missing-aria"), - "link-missing-labelling": require("./rules/link-missing-labelling"), - "input-components-require-accessible-name": require("./rules/input-components-require-accessible-name"), - "menu-item-needs-labelling": require("./rules/menu-item-needs-labelling"), - "switch-needs-labelling": require("./rules/switch-needs-labelling"), - "toolbar-missing-aria": require("./rules/toolbar-missing-aria"), - "combobox-needs-labelling": require("./rules/combobox-needs-labelling"), - "no-empty-components": require("./rules/no-empty-components"), - "accordion-header-needs-labelling": require("./rules/accordion-header-needs-labelling"), - "accordion-item-needs-header-and-panel": require("./rules/accordion-item-needs-header-and-panel"), - "compound-button-needs-labelling": require("./rules/buttons/compound-button-needs-labelling"), - "no-empty-buttons": require("./rules/buttons/no-empty-buttons"), - "spin-button-needs-labelling": require("./rules/spin-button-needs-labelling"), - "spin-button-unrecommended-labelling": require("./rules/spin-button-unrecommended-labelling"), - "breadcrumb-needs-labelling": require("./rules/breadcrumb-needs-labelling"), - "dropdown-needs-labelling": require("./rules/dropdown-needs-labelling"), - "tooltip-not-recommended": require("./rules/tooltip-not-recommended"), - "avatar-needs-name": require("./rules/avatar-needs-name"), - "radio-button-missing-label": require("./rules/radio-button-missing-label"), - "radiogroup-missing-label": require("./rules/radiogroup-missing-label"), + "checkbox-needs-labelling": checkboxNeedsLabelling, + "image-button-missing-aria": imageButtonMissingAria, + "link-missing-labelling": linkMissingLabelling, + "input-components-require-accessible-name": inputComponentsRequireAccessibleName, + "menu-item-needs-labelling": menuItemNeedsLabelling, + "switch-needs-labelling": switchNeedsLabelling, + "toolbar-missing-aria": toolbarMissingAria, + "combobox-needs-labelling": comboboxNeedsLabelling, + "no-empty-components": noEmptyComponents, + "accordion-header-needs-labelling": accordionHeaderNeedsLabelling, + "accordion-item-needs-header-and-panel": accordionItemNeedsHeaderAndPanel, + "compound-button-needs-labelling": compoundButtonNeedsLabelling, + "no-empty-buttons": noEmptyButtons, + "spin-button-needs-labelling": spinButtonNeedsLabelling, + "spin-button-unrecommended-labelling": spinButtonUnrecommendedLabelling, + "breadcrumb-needs-labelling": breadcrumbNeedsLabelling, + "dropdown-needs-labelling": dropwdonNeedsLabelling, + "tooltip-not-recommended": tooltipNotRecommended, + "avatar-needs-name": avatarNeedsName, + "radio-button-missing-label": radioButtonMissingLabel, + "radiogroup-missing-label": radiogroupMissingLabel, "prefer-aria-over-title-attribute": preferAriaOverTitleAttribute, - "dialogbody-needs-title-content-and-actions": require("./rules/dialogbody-needs-title-content-and-actions"), - "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") + "dialogbody-needs-title-content-and-actions": dialogbodyNeedsTitleContentAndActions, + "dialogsurface-needs-aria": dialogsurfaceNeedsAria, + "spinner-needs-labelling": spinnerNeedsLabelling, + "badge-needs-accessible-name": badgeNeedsAccessibleName, + "progressbar-needs-labelling": progressbarNeedsLabelling }, configs: { recommended: { diff --git a/lib/rules/accordion-header-needs-labelling.js b/lib/rules/accordion-header-needs-labelling.ts similarity index 70% rename from lib/rules/accordion-header-needs-labelling.js rename to lib/rules/accordion-header-needs-labelling.ts index b5b4974..943308b 100644 --- a/lib/rules/accordion-header-needs-labelling.js +++ b/lib/rules/accordion-header-needs-labelling.ts @@ -1,21 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const { hasToolTipParent } = require("../util/hasTooltipParent"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); -var hasProp = require("jsx-ast-utils").hasProp; -var elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType, hasProp } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasToolTipParent } from "../util/hasTooltipParent"; +import { hasTextContentChild } from "../util/hasTextContentChild"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { messages: { missingAriaLabel: "Accessibility: the accordion header must have an accessible name" @@ -24,21 +23,19 @@ module.exports = { docs: { description: "The accordion header is a button and it needs an accessibile name e.g. text content, aria-label, aria-labelledby.", - recommended: false, - url: null // URL to the documentation page for this rule + recommended: false }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, // 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 - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a AccordionHeader, return - if (elementType(openingElement) !== "AccordionHeader") { + if (elementType(openingElement as JSXOpeningElement) !== "AccordionHeader") { return; } @@ -48,7 +45,10 @@ module.exports = { } // if it is not an icon button, return - if (!hasProp(openingElement.attributes, "icon") && !hasProp(openingElement.attributes, "expandIcon")) { + if ( + !hasProp(openingElement.attributes as JSXAttribute[], "icon") && + !hasProp(openingElement.attributes as JSXAttribute[], "expandIcon") + ) { return; } @@ -75,4 +75,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/accordion-item-needs-header-and-panel.js b/lib/rules/accordion-item-needs-header-and-panel.ts similarity index 51% rename from lib/rules/accordion-item-needs-header-and-panel.js rename to lib/rules/accordion-item-needs-header-and-panel.ts index b3e62c0..97611cf 100644 --- a/lib/rules/accordion-item-needs-header-and-panel.js +++ b/lib/rules/accordion-item-needs-header-and-panel.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; +import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { messages: { accordionItemOneHeaderOnePanel: "ensure AccordionItem has exactly one header and one panel" @@ -16,25 +16,38 @@ module.exports = { type: "problem", // `problem`, `suggestion`, or `layout` docs: { description: "An AccordionItem needs exactly one header and one panel", - recommended: true, + recommended: "strict", url: "https://www.w3.org/WAI/ARIA/apg/patterns/accordion/" // URL to the documentation page for this rule }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, create(context) { return { - JSXOpeningElement(node) { - if (node.name.name !== "AccordionItem") { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { + if (node.name.type === AST_NODE_TYPES.JSXIdentifier && node.name.name !== "AccordionItem") { + return; + } + + if (!(node.parent && node.parent.type === AST_NODE_TYPES.JSXElement)) { return; } const children = node.parent.children.filter(child => child.type === "JSXElement"); - const hasOneHeader = children.filter(child => child.openingElement.name.name === "AccordionHeader").length === 1; + const hasOneHeader = + children.filter( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "AccordionHeader" + ).length === 1; - const hasOnePanel = children.filter(child => child.openingElement.name.name === "AccordionPanel").length === 1; + const hasOnePanel = + children.filter( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "AccordionPanel" + ).length === 1; if (!hasOneHeader || !hasOnePanel || children.length !== 2) { context.report({ @@ -45,5 +58,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/avatar-needs-name.js b/lib/rules/avatar-needs-name.ts similarity index 76% rename from lib/rules/avatar-needs-name.js rename to lib/rules/avatar-needs-name.ts index 4578b03..5db732e 100644 --- a/lib/rules/avatar-needs-name.js +++ b/lib/rules/avatar-needs-name.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { JSXOpeningElement } from "estree-jsx"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -23,7 +24,7 @@ module.exports = { docs: { // DONE description: "Accessibility: Avatar must have an accessible labelling: name, aria-label, aria-labelledby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -32,9 +33,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not an Avatar, return - if (elementType(node) !== "Avatar") { + if (elementType(node as JSXOpeningElement) !== "Avatar") { return; } @@ -55,4 +56,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js b/lib/rules/avoid-using-aria-describedby-for-primary-labelling.ts similarity index 73% rename from lib/rules/avoid-using-aria-describedby-for-primary-labelling.js rename to lib/rules/avoid-using-aria-describedby-for-primary-labelling.ts index 8878738..431e594 100644 --- a/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js +++ b/lib/rules/avoid-using-aria-describedby-for-primary-labelling.ts @@ -1,29 +1,28 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { applicableComponents: inputComponents } = require("../applicableComponents/inputBasedComponents"); -const { applicableComponents: buttonComponents } = require("../applicableComponents/buttonBasedComponents"); -const { elementType } = require("jsx-ast-utils"); -const { +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { JSXOpeningElement } from "estree-jsx"; +import { elementType } from "jsx-ast-utils"; +import { applicableComponents as inputComponents } from "../applicableComponents/inputBasedComponents"; +import { applicableComponents as buttonComponents } from "../applicableComponents/buttonBasedComponents"; +import { isInsideLabelTag, hasAssociatedLabelViaHtmlFor, hasAssociatedLabelViaAriaLabelledBy, hasAssociatedLabelViaAriaDescribedby -} = require("../util/labelUtils"); - -const { hasFieldParent } = require("../util/hasFieldParent"); -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const { hasToolTipParent } = require("../util/hasTooltipParent"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); +} from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasToolTipParent } from "../util/hasTooltipParent"; +import { hasTextContentChild } from "../util/hasTextContentChild"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { messages: { noAriaDescribedbyAsLabel: "Accessibility: aria-describedby provides additional context and is not meant for primary labeling." @@ -31,20 +30,18 @@ module.exports = { type: "suggestion", // `problem`, `suggestion`, or `layout` docs: { description: "aria-describedby provides additional context and is not meant for primary labeling.", - recommended: true, - url: null // URL to the documentation page for this rule + recommended: "strict" }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, create(context) { return { - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; if ( - buttonComponents.includes(elementType(openingElement)) && // It's a button-based component + buttonComponents.includes(elementType(openingElement as JSXOpeningElement)) && // It's a button-based component !hasToolTipParent(context) && // It doesn't have a tooltip parent !hasTextContentChild(node) && // It doesn't have text content !hasNonEmptyProp(openingElement.attributes, "title") && // Doesn't have a title @@ -59,7 +56,7 @@ module.exports = { } if ( - inputComponents.includes(elementType(openingElement)) && // It's an input component + inputComponents.includes(elementType(openingElement as JSXOpeningElement)) && // It's an input component !hasFieldParent(context) && // It doesn't have a field parent !isInsideLabelTag(context) && // It's not inside a label tag !hasAssociatedLabelViaHtmlFor(openingElement, context) && // Doesn't have a label via htmlFor @@ -74,4 +71,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/badge-needs-accessible-name.js b/lib/rules/badge-needs-accessible-name.ts similarity index 76% rename from lib/rules/badge-needs-accessible-name.js rename to lib/rules/badge-needs-accessible-name.ts index cbc35ab..089f413 100644 --- a/lib/rules/badge-needs-accessible-name.js +++ b/lib/rules/badge-needs-accessible-name.ts @@ -1,19 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -var elementType = require("jsx-ast-utils").elementType; -const { getPropValue, getProp } = require("jsx-ast-utils"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); -var hasProp = require("jsx-ast-utils").hasProp; +import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType, hasProp } from "jsx-ast-utils"; +import { getPropValue, getProp } from "jsx-ast-utils"; +import { hasTextContentChild } from "../util/hasTextContentChild"; +import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -34,8 +33,8 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXElement(node) { - const openingElement = node.openingElement; + JSXElement(node: TSESTree.JSXElement) { + const openingElement = node.openingElement as JSXOpeningElement; // If it's not a Badge component, return early if (elementType(openingElement) !== "Badge") { @@ -53,13 +52,13 @@ module.exports = { const hasIconProp = hasProp(openingElement.attributes, "icon"); if (hasIconProp) { - const iconProp = getProp(openingElement.attributes, "icon"); + const iconProp = getProp(openingElement.attributes, "icon") as TSESTree.JSXAttribute; - if (iconProp) { - const iconElement = iconProp.value.expression; + if (iconProp && iconProp.value && iconProp.value.type === AST_NODE_TYPES.JSXExpressionContainer) { + const iconElement = iconProp.value.expression as TSESTree.JSXElement; // Check if the icon has an aria-label - const ariaLabelAttr = hasProp(iconElement.openingElement.attributes, "aria-label"); + const ariaLabelAttr = hasProp(iconElement.openingElement.attributes as JSXAttribute[], "aria-label"); // Report an error if aria-label is missing if (!ariaLabelAttr) { @@ -76,8 +75,9 @@ module.exports = { } // Simplified logic to check for a color-only Badge (no icon, no text) + const roleProp = getProp(openingElement.attributes, "role"); const hasColorProp = hasProp(openingElement.attributes, "color"); - const hasRole = getPropValue(getProp(openingElement.attributes, "role")) === "img"; + const hasRole = !!roleProp && getPropValue(roleProp) === "img"; const hasAriaLabel = hasProp(openingElement.attributes, "aria-label"); // If it's color-only, ensure it has role="img" and aria-label @@ -91,12 +91,14 @@ module.exports = { // Fix role by adding role="img" if (!hasRole) { - fixes.push(fixer.insertTextAfter(openingElement.name, ' role="img"')); + fixes.push(fixer.insertTextAfter((openingElement as TSESTree.JSXOpeningElement).name, ' role="img"')); } // Fix aria-label by adding aria-label="" if (!hasAriaLabel) { - fixes.push(fixer.insertTextAfter(openingElement.name, ' aria-label=""')); + fixes.push( + fixer.insertTextAfter((openingElement as TSESTree.JSXOpeningElement).name, ' aria-label=""') + ); } return fixes; @@ -112,5 +114,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/breadcrumb-needs-labelling.js b/lib/rules/breadcrumb-needs-labelling.ts similarity index 76% rename from lib/rules/breadcrumb-needs-labelling.js rename to lib/rules/breadcrumb-needs-labelling.ts index c157e56..ee1f746 100644 --- a/lib/rules/breadcrumb-needs-labelling.js +++ b/lib/rules/breadcrumb-needs-labelling.ts @@ -1,18 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -25,16 +25,15 @@ module.exports = { recommended: false, url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Breadcrumb, return - if (elementType(node) !== "Breadcrumb") { + if (elementType(node as JSXOpeningElement) !== "Breadcrumb") { return; } @@ -54,5 +53,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/buttons/compound-button-needs-labelling.js b/lib/rules/buttons/compound-button-needs-labelling.ts similarity index 76% rename from lib/rules/buttons/compound-button-needs-labelling.js rename to lib/rules/buttons/compound-button-needs-labelling.ts index 14db29f..e4cdd46 100644 --- a/lib/rules/buttons/compound-button-needs-labelling.js +++ b/lib/rules/buttons/compound-button-needs-labelling.ts @@ -1,19 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../../util/hasNonEmptyProp"); -const { hasToolTipParent } = require("../../util/hasTooltipParent"); -const { hasTextContentChild } = require("../../util/hasTextContentChild"); -const { hasAssociatedLabelViaAriaLabelledBy } = require("../../util/labelUtils"); -var elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../../util/hasNonEmptyProp"; +import { hasToolTipParent } from "../../util/hasTooltipParent"; +import { hasTextContentChild } from "../../util/hasTextContentChild"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../../util/labelUtils"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -25,7 +26,7 @@ module.exports = { docs: { description: "Accessibility: Compound buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -34,11 +35,11 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a Compound button, return - if (elementType(openingElement) !== "CompoundButton") { + if (elementType(openingElement as JSXOpeningElement) !== "CompoundButton") { return; } @@ -65,4 +66,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/buttons/image-button-missing-aria.js b/lib/rules/buttons/image-button-missing-aria.ts similarity index 74% rename from lib/rules/buttons/image-button-missing-aria.js rename to lib/rules/buttons/image-button-missing-aria.ts index 06fad1c..9cd6ff5 100644 --- a/lib/rules/buttons/image-button-missing-aria.js +++ b/lib/rules/buttons/image-button-missing-aria.ts @@ -1,21 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../../util/hasNonEmptyProp"); -const { hasToolTipParent } = require("../../util/hasTooltipParent"); -const { hasTextContentChild } = require("../../util/hasTextContentChild"); -const { hasAssociatedLabelViaAriaLabelledBy } = require("../../util/labelUtils"); -const { applicableComponents } = require("../../applicableComponents/buttonBasedComponents"); -var hasProp = require("jsx-ast-utils").hasProp; -var elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType, hasProp } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../../util/hasNonEmptyProp"; +import { hasToolTipParent } from "../../util/hasTooltipParent"; +import { hasTextContentChild } from "../../util/hasTextContentChild"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../../util/labelUtils"; +import { applicableComponents } from "../../applicableComponents/buttonBasedComponents"; +import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -27,7 +27,7 @@ module.exports = { docs: { description: "Accessibility: Image buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -36,16 +36,16 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a button, return - if (!applicableComponents.includes(elementType(openingElement))) { + if (!applicableComponents.includes(elementType(openingElement as JSXOpeningElement))) { return; } // if it is not an icon button, return - if (!hasProp(openingElement.attributes, "icon")) { + if (!hasProp(openingElement.attributes as JSXAttribute[], "icon")) { return; } @@ -77,4 +77,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/buttons/no-empty-buttons.js b/lib/rules/buttons/no-empty-buttons.ts similarity index 72% rename from lib/rules/buttons/no-empty-buttons.js rename to lib/rules/buttons/no-empty-buttons.ts index a48815d..ec2158f 100644 --- a/lib/rules/buttons/no-empty-buttons.js +++ b/lib/rules/buttons/no-empty-buttons.ts @@ -1,19 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasTextContentChild } = require("../../util/hasTextContentChild"); -const { hasNonEmptyProp } = require("../../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -var hasProp = require("jsx-ast-utils").hasProp; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType, hasProp } from "jsx-ast-utils"; +import { hasTextContentChild } from "../../util/hasTextContentChild"; +import { hasNonEmptyProp } from "../../util/hasNonEmptyProp"; +import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ const allowedComponents = ["Button", "ToggleButton", "SplitButton", "MenuButton", "CompoundButton"]; -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the lint rule messages: { @@ -24,7 +24,7 @@ module.exports = { // docs for the rule docs: { description: `Accessibility: ${allowedComponents.join(", ")} must either text content or icon or child component`, - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] // no options @@ -33,11 +33,11 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a button, return - if (!allowedComponents.includes(elementType(openingElement))) { + if (!allowedComponents.includes(elementType(openingElement as JSXOpeningElement))) { return; } @@ -45,12 +45,15 @@ module.exports = { if (hasTextContentChild(node)) return; // if there is icon prop, return - if (hasProp(openingElement.attributes, "icon")) { + if (hasProp(openingElement.attributes as JSXAttribute[], "icon")) { return; } // if split button has secondary content, return - if (elementType(openingElement) === "CompoundButton" && hasNonEmptyProp(openingElement.attribute, "secondaryContent")) { + if ( + elementType(openingElement as JSXOpeningElement) === "CompoundButton" && + hasNonEmptyProp(openingElement.attributes, "secondaryContent") + ) { return; } @@ -66,4 +69,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/checkbox-needs-labelling.js b/lib/rules/checkbox-needs-labelling.ts similarity index 75% rename from lib/rules/checkbox-needs-labelling.js rename to lib/rules/checkbox-needs-labelling.ts index 33f776c..e16eea9 100644 --- a/lib/rules/checkbox-needs-labelling.js +++ b/lib/rules/checkbox-needs-labelling.ts @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { elementType } from "jsx-ast-utils"; +import { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -24,7 +25,7 @@ module.exports = { docs: { // DONE description: "Accessibility: Checkbox without label must have an accessible and visual label: aria-labelledby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -33,9 +34,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Checkbox, return - if (elementType(node) !== "Checkbox") { + if (elementType(node as JSXOpeningElement) !== "Checkbox") { return; } @@ -58,4 +59,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/combobox-needs-labelling.js b/lib/rules/combobox-needs-labelling.ts similarity index 75% rename from lib/rules/combobox-needs-labelling.js rename to lib/rules/combobox-needs-labelling.ts index 919ac9a..a5d2387 100644 --- a/lib/rules/combobox-needs-labelling.js +++ b/lib/rules/combobox-needs-labelling.ts @@ -1,19 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -26,16 +26,15 @@ module.exports = { recommended: false, url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Combobox, return - if (elementType(node) !== "Combobox") { + if (elementType(node as JSXOpeningElement) !== "Combobox") { return; } @@ -58,5 +57,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/dialogbody-needs-title-content-and-actions.js b/lib/rules/dialogbody-needs-title-content-and-actions.js deleted file mode 100644 index 62f09b9..0000000 --- a/lib/rules/dialogbody-needs-title-content-and-actions.js +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -"use strict"; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - messages: { - dialogBodyOneTitleOneContentOneFooter: "ensure DialogBody has exactly one header,one content and one footer" - }, - type: "problem", // `problem`, `suggestion`, or `layout` - docs: { - description: "A DialogBody should have a header(DialogTitle), content(DialogContent), and footer(DialogActions)", - recommended: true, - url: "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/" // URL to the documentation page for this rule - }, - fixable: null, // Or `code` or `whitespace` - schema: [] // Add a schema if the rule has options - }, - - create(context) { - return { - JSXOpeningElement(node) { - if (node.name.name !== "DialogBody") { - return; - } - - const children = node.parent.children.filter(child => child.type === "JSXElement"); - - const hasOneTitle = children.filter(child => child.openingElement.name.name === "DialogTitle").length === 1; - - const hasOneContnet = children.filter(child => child.openingElement.name.name === "DialogContent").length === 1; - - const hasOneAction = children.filter(child => child.openingElement.name.name === "DialogActions").length === 1; - - if (!hasOneTitle || !hasOneContnet || !hasOneAction || children.length !== 3) { - context.report({ - node, - messageId: "dialogBodyOneTitleOneContentOneFooter" - }); - } - } - }; - } -}; diff --git a/lib/rules/dialogbody-needs-title-content-and-actions.ts b/lib/rules/dialogbody-needs-title-content-and-actions.ts new file mode 100644 index 0000000..a9adaa1 --- /dev/null +++ b/lib/rules/dialogbody-needs-title-content-and-actions.ts @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], + meta: { + messages: { + dialogBodyOneTitleOneContentOneFooter: "ensure DialogBody has exactly one header,one content and one footer" + }, + type: "problem", // `problem`, `suggestion`, or `layout` + docs: { + description: "A DialogBody should have a header(DialogTitle), content(DialogContent), and footer(DialogActions)", + recommended: "strict", + url: "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/" // URL to the documentation page for this rule + }, + schema: [] // Add a schema if the rule has options + }, + + create(context) { + return { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { + if (node.name.type === AST_NODE_TYPES.JSXIdentifier && node.name.name !== "DialogBody") { + return; + } + + const children = + node.parent && + node.parent.type === AST_NODE_TYPES.JSXElement && + node.parent.children.filter(child => child.type === "JSXElement"); + + if (children) { + const hasOneTitle = + children.filter( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "DialogTitle" + ).length === 1; + + const hasOneContnet = + children.filter( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "DialogContent" + ).length === 1; + + const hasOneAction = + children.filter( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "DialogActions" + ).length === 1; + + if (!hasOneTitle || !hasOneContnet || !hasOneAction || children.length !== 3) { + context.report({ + node, + messageId: "dialogBodyOneTitleOneContentOneFooter" + }); + } + } + } + }; + } +}); + +export default rule; diff --git a/lib/rules/dialogsurface-needs-aria.js b/lib/rules/dialogsurface-needs-aria.js deleted file mode 100644 index b1a71da..0000000 --- a/lib/rules/dialogsurface-needs-aria.js +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -"use strict"; - -const { hasAssociatedAriaText } = require("../util/labelUtils"); -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); -var elementType = require("jsx-ast-utils").elementType; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - // possible error messages for the rule - messages: { - missingAriaOnDialogSurface: "DialogueSurface need accessible labelling: aria-describedby on DialogueSurface and aria-label or aria-labelledby(if DialogueTitle is missing)" - }, - // "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: "DialogueSurface need accessible labelling: aria-describedby on DialogueSurface and aria-label or aria-labelledby(if DialogueTitle is missing)", - recommended: true, - url: "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/" // URL to the documentation page for this rule - }, - schema: [] - }, - - create(context) { - return { - // visitor functions for different types of nodes - JSXOpeningElement(node) { - - // if it is not a DialogSurface, return - if (elementType(node) !== "DialogSurface") { - return; - } - - // determine if DialogSurface as aria-describedby - const hasAriaDescribedBy = hasAssociatedAriaText(node, context, "aria-describedby"); - - // find DialogBody Component - const dialogueSurfaceChildren = node.parent.children.filter(child => child.type === "JSXElement"); - - const DialogBodyNode = dialogueSurfaceChildren.find(child => child.openingElement.name.name === "DialogBody"); - - - // find DialogTitle inside DialogBody Component - const dialogueBodyChildren = DialogBodyNode && DialogBodyNode.children.filter(child => child.type === "JSXElement"); - - const DialogTitleNode = dialogueBodyChildren && dialogueBodyChildren.find(child => child.openingElement.name.name === "DialogTitle"); - - // determine if DialogueText has any text content - const hasDialogTitleText = DialogTitleNode && hasTextContentChild(DialogTitleNode); - - // determine if DialogueText or aria-label is present - const hasTitleOrAriaLabelledBy = hasDialogTitleText || hasNonEmptyProp(node.attributes, "aria-label") || hasAssociatedAriaText(node, context, "aria-labelledby"); - - // if the DialogSurface has aria labelling and description, return - if (hasAriaDescribedBy && hasTitleOrAriaLabelledBy) { - return; - } - - context.report({ - node, - messageId: `missingAriaOnDialogSurface` - }); - } - }; - } -}; - diff --git a/lib/rules/dialogsurface-needs-aria.ts b/lib/rules/dialogsurface-needs-aria.ts new file mode 100644 index 0000000..dcb59ed --- /dev/null +++ b/lib/rules/dialogsurface-needs-aria.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasAssociatedAriaText } from "../util/labelUtils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasTextContentChild } from "../util/hasTextContentChild"; +import { JSXOpeningElement } from "estree-jsx"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], + meta: { + // possible error messages for the rule + messages: { + missingAriaOnDialogSurface: + "DialogueSurface need accessible labelling: aria-describedby on DialogueSurface and aria-label or aria-labelledby(if DialogueTitle is missing)" + }, + // "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: + "DialogueSurface need accessible labelling: aria-describedby on DialogueSurface and aria-label or aria-labelledby(if DialogueTitle is missing)", + recommended: "strict", + url: "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/" // 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 DialogSurface, return + if (elementType(node as JSXOpeningElement) !== "DialogSurface") { + return; + } + + // determine if DialogSurface as aria-describedby + const hasAriaDescribedBy = hasAssociatedAriaText(node, context, "aria-describedby"); + + // find DialogBody Component + const dialogueSurfaceChildren = + node.parent && + node.parent.type === AST_NODE_TYPES.JSXElement && + node.parent.children.filter(child => child.type === "JSXElement"); + + if (dialogueSurfaceChildren) { + const DialogBodyNode = dialogueSurfaceChildren.find( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "DialogBody" + ); + + // find DialogTitle inside DialogBody Component + const dialogueBodyChildren = DialogBodyNode && DialogBodyNode.children.filter(child => child.type === "JSXElement"); + + const DialogTitleNode = + dialogueBodyChildren && + dialogueBodyChildren.find( + child => + child.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && + child.openingElement.name.name === "DialogTitle" + ); + + // determine if DialogueText has any text content + const hasDialogTitleText = DialogTitleNode && hasTextContentChild(DialogTitleNode); + + // determine if DialogueText or aria-label is present + const hasTitleOrAriaLabelledBy = + hasDialogTitleText || + hasNonEmptyProp(node.attributes, "aria-label") || + hasAssociatedAriaText(node, context, "aria-labelledby"); + + // if the DialogSurface has aria labelling and description, return + if (hasAriaDescribedBy && hasTitleOrAriaLabelledBy) { + return; + } + + context.report({ + node, + messageId: `missingAriaOnDialogSurface` + }); + } + } + }; + } +}); + +export default rule; diff --git a/lib/rules/dropdown-needs-labelling.js b/lib/rules/dropdown-needs-labelling.ts similarity index 77% rename from lib/rules/dropdown-needs-labelling.js rename to lib/rules/dropdown-needs-labelling.ts index f87e46b..8141c82 100644 --- a/lib/rules/dropdown-needs-labelling.js +++ b/lib/rules/dropdown-needs-labelling.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, hasAssociatedLabelViaHtmlFor, isInsideLabelTag } = require("../util/labelUtils"); -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasAssociatedLabelViaAriaLabelledBy, hasAssociatedLabelViaHtmlFor, isInsideLabelTag } from "../util/labelUtils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -22,8 +23,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: Dropdown menu must have an id and it needs to be linked via htmlFor of a Label", - recommended: true, - url: null + recommended: "strict" }, schema: [] }, @@ -32,9 +32,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Dropdown, return - if (elementType(node) !== "Dropdown") { + if (elementType(node as JSXOpeningElement) !== "Dropdown") { return; } @@ -59,4 +59,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/input-components-require-accessible-name.js b/lib/rules/input-components-require-accessible-name.ts similarity index 74% rename from lib/rules/input-components-require-accessible-name.js rename to lib/rules/input-components-require-accessible-name.ts index 7068829..9fa0c88 100644 --- a/lib/rules/input-components-require-accessible-name.js +++ b/lib/rules/input-components-require-accessible-name.ts @@ -1,19 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { elementType } = require("jsx-ast-utils"); - -const { isInsideLabelTag, hasAssociatedLabelViaHtmlFor, hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); -const { applicableComponents } = require("../applicableComponents/inputBasedComponents"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { isInsideLabelTag, hasAssociatedLabelViaHtmlFor, hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { applicableComponents } from "../applicableComponents/inputBasedComponents"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -26,7 +26,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: Input fields must have accessible labelling: aria-label, aria-labelledby or an associated label", - recommended: true, + recommended: "strict", url: "https://www.w3.org/WAI/tutorials/forms/labels/" // URL to the documentation page for this rule }, schema: [] @@ -35,9 +35,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a listed component, return - if (!applicableComponents.includes(elementType(node))) { + if (!applicableComponents.includes(elementType(node as JSXOpeningElement))) { return; } @@ -58,4 +58,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/link-missing-labelling.js b/lib/rules/link-missing-labelling.ts similarity index 81% rename from lib/rules/link-missing-labelling.js rename to lib/rules/link-missing-labelling.ts index 845e9ce..a086394 100644 --- a/lib/rules/link-missing-labelling.js +++ b/lib/rules/link-missing-labelling.ts @@ -1,27 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { elementType } = require("jsx-ast-utils"); -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); -const { hasLabelledChildImage } = require("../util/hasLabelledChildImage"); -const { linkBasedComponents } = require("../applicableComponents/linkBasedComponents"); -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasTextContentChild } from "../util/hasTextContentChild"; +import { hasLabelledChildImage } from "../util/hasLabelledChildImage"; +import { linkBasedComponents } from "../applicableComponents/linkBasedComponents"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { type: "problem", docs: { description: "Accessibility: Image links must have an accessible name. Add either text content, labelling to the image or labelling to the link itself.", - recommended: true, + recommended: "strict", url: "https://www.w3.org/WAI/standards-guidelines/act/rules/c487ae/" // URL to the documentation page for this rule }, messages: { @@ -40,11 +40,11 @@ module.exports = { return { // visitor functions for different types of nodes - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it's not a link based component, return - if (!linkBasedComponents.includes(elementType(openingElement))) { + if (!linkBasedComponents.includes(elementType(openingElement as JSXOpeningElement))) { return; } @@ -90,4 +90,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/menu-item-needs-labelling.js b/lib/rules/menu-item-needs-labelling.ts similarity index 74% rename from lib/rules/menu-item-needs-labelling.js rename to lib/rules/menu-item-needs-labelling.ts index 6bc4e2a..e1d2169 100644 --- a/lib/rules/menu-item-needs-labelling.js +++ b/lib/rules/menu-item-needs-labelling.ts @@ -1,20 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); -const { hasToolTipParent } = require("../util/hasTooltipParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { hasTextContentChild } from "../util/hasTextContentChild"; +import { hasToolTipParent } from "../util/hasTooltipParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -24,20 +24,19 @@ module.exports = { type: "problem", docs: { description: "Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, create(context) { return { // visitor functions for different types of nodes - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a MenuItem, return - if (elementType(openingElement) !== "MenuItem") { + if (elementType(openingElement as JSXOpeningElement) !== "MenuItem") { return; } @@ -59,5 +58,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/no-empty-components.js b/lib/rules/no-empty-components.ts similarity index 81% rename from lib/rules/no-empty-components.js rename to lib/rules/no-empty-components.ts index b023967..6e4f61a 100644 --- a/lib/rules/no-empty-components.js +++ b/lib/rules/no-empty-components.ts @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -var elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { JSXOpeningElement } from "estree-jsx"; +import { elementType } from "jsx-ast-utils"; //------------------------------------------------------------------------------ // Rule Definition @@ -11,8 +11,8 @@ var elementType = require("jsx-ast-utils").elementType; // Define an array of allowed component names const allowedComponents = ["Text", "Label", "Combobox", "Breadcrumb", "Dropdown", "Accordion", "AccordionItem", "AccordionPanel"]; -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the lint rule messages: { @@ -21,21 +21,19 @@ module.exports = { type: "problem", // `problem`, `suggestion`, or `layout` docs: { description: "FluentUI components should not be empty", - recommended: true, - url: null // URL to the documentation page for this rule + recommended: "strict" }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, // 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 - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a listed component, return - if (!allowedComponents.includes(elementType(openingElement))) { + if (!allowedComponents.includes(elementType(openingElement as JSXOpeningElement))) { return; } @@ -51,4 +49,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/progressbar-needs-labelling.js b/lib/rules/progressbar-needs-labelling.ts similarity index 84% rename from lib/rules/progressbar-needs-labelling.js rename to lib/rules/progressbar-needs-labelling.ts index a580aea..37ad191 100644 --- a/lib/rules/progressbar-needs-labelling.js +++ b/lib/rules/progressbar-needs-labelling.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasFieldParent } = require("../util/hasFieldParent"); -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -24,7 +25,7 @@ module.exports = { docs: { description: "Accessibility: Progressbar must have aria-valuemin, aria-valuemax, aria-valuenow, aria-describedby and either aria-label or aria-labelledby attributes", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -33,9 +34,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a ProgressBar, return - if (elementType(node) !== "ProgressBar") { + if (elementType(node as JSXOpeningElement) !== "ProgressBar") { return; } @@ -73,5 +74,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/radio-button-missing-label.js b/lib/rules/radio-button-missing-label.ts similarity index 76% rename from lib/rules/radio-button-missing-label.js rename to lib/rules/radio-button-missing-label.ts index 9e76123..fa7c791 100644 --- a/lib/rules/radio-button-missing-label.js +++ b/lib/rules/radio-button-missing-label.ts @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -24,7 +25,7 @@ module.exports = { docs: { // DONE description: "Accessibility: Radio button without label must have an accessible and visual label: aria-labelledby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -33,9 +34,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Checkbox, return - if (elementType(node) !== "Radio") { + if (elementType(node as JSXOpeningElement) !== "Radio") { return; } @@ -59,4 +60,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/radiogroup-missing-label.js b/lib/rules/radiogroup-missing-label.ts similarity index 75% rename from lib/rules/radiogroup-missing-label.js rename to lib/rules/radiogroup-missing-label.ts index 9be7dc1..eb585f2 100644 --- a/lib/rules/radiogroup-missing-label.js +++ b/lib/rules/radiogroup-missing-label.ts @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -24,7 +25,7 @@ module.exports = { docs: { // DONE description: "Accessibility: RadioGroup without label must have an accessible and visual label: aria-labelledby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -33,9 +34,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Checkbox, return - if (elementType(node) !== "RadioGroup") { + if (elementType(node as JSXOpeningElement) !== "RadioGroup") { return; } @@ -59,4 +60,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/spin-button-needs-labelling.js b/lib/rules/spin-button-needs-labelling.ts similarity index 75% rename from lib/rules/spin-button-needs-labelling.js rename to lib/rules/spin-button-needs-labelling.ts index 181a57b..d6272f8 100644 --- a/lib/rules/spin-button-needs-labelling.js +++ b/lib/rules/spin-button-needs-labelling.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -22,7 +23,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: SpinButtons must have an accessible label", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -31,9 +32,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a SpinButton, return - if (elementType(node) !== "SpinButton") { + if (elementType(node as JSXOpeningElement) !== "SpinButton") { return; } @@ -55,4 +56,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/spin-button-unrecommended-labelling.js b/lib/rules/spin-button-unrecommended-labelling.ts similarity index 75% rename from lib/rules/spin-button-unrecommended-labelling.js rename to lib/rules/spin-button-unrecommended-labelling.ts index a713d83..e10b6c4 100644 --- a/lib/rules/spin-button-unrecommended-labelling.js +++ b/lib/rules/spin-button-unrecommended-labelling.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const { hasToolTipParent } = require("../util/hasTooltipParent"); -var elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasToolTipParent } from "../util/hasTooltipParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible suggestion messages for the rule messages: { @@ -22,7 +23,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: Unrecommended accessibility labelling - SpinButton", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -31,9 +32,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a SpinButton, return - if (elementType(node) !== "SpinButton") { + if (elementType(node as JSXOpeningElement) !== "SpinButton") { return; } @@ -47,5 +48,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/spinner-needs-labelling.js b/lib/rules/spinner-needs-labelling.ts similarity index 67% rename from lib/rules/spinner-needs-labelling.js rename to lib/rules/spinner-needs-labelling.ts index cc1dc58..5005cc6 100644 --- a/lib/rules/spinner-needs-labelling.js +++ b/lib/rules/spinner-needs-labelling.ts @@ -1,16 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -21,7 +22,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: Spinner must have either aria-label or label, aria-live and aria-busy attributes", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -30,15 +31,16 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Spinner, return - if (elementType(node) !== "Spinner") { + if (elementType(node as JSXOpeningElement) !== "Spinner") { return; } - if (hasNonEmptyProp(node.attributes, "aria-busy") - && hasNonEmptyProp(node.attributes, "aria-live") - && (hasNonEmptyProp(node.attributes, "label") || hasNonEmptyProp(node.attributes, "aria-label")) + if ( + hasNonEmptyProp(node.attributes, "aria-busy") && + hasNonEmptyProp(node.attributes, "aria-live") && + (hasNonEmptyProp(node.attributes, "label") || hasNonEmptyProp(node.attributes, "aria-label")) ) { return; } @@ -51,5 +53,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/switch-needs-labelling.js b/lib/rules/switch-needs-labelling.ts similarity index 74% rename from lib/rules/switch-needs-labelling.js rename to lib/rules/switch-needs-labelling.ts index f2e1d5b..648a6f3 100644 --- a/lib/rules/switch-needs-labelling.js +++ b/lib/rules/switch-needs-labelling.ts @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; -const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); -const { hasFieldParent } = require("../util/hasFieldParent"); +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } from "../util/labelUtils"; +import { hasFieldParent } from "../util/hasFieldParent"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -23,7 +24,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: Switch must have an accessible label", - recommended: true, + recommended: "strict", url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule }, schema: [] @@ -32,9 +33,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Switch, return - if (elementType(node) !== "Switch") { + if (elementType(node as JSXOpeningElement) !== "Switch") { return; } @@ -57,4 +58,6 @@ module.exports = { } }; } -}; +}); + +export default rule; diff --git a/lib/rules/toolbar-missing-aria.js b/lib/rules/toolbar-missing-aria.ts similarity index 73% rename from lib/rules/toolbar-missing-aria.js rename to lib/rules/toolbar-missing-aria.ts index f7438db..f725458 100644 --- a/lib/rules/toolbar-missing-aria.js +++ b/lib/rules/toolbar-missing-aria.ts @@ -1,18 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -var elementType = require("jsx-ast-utils").elementType; +import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { elementType } from "jsx-ast-utils"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { JSXOpeningElement } from "estree-jsx"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -23,7 +23,7 @@ module.exports = { // docs for the rule docs: { description: "Accessibility: Toolbars need accessible labelling: aria-label or aria-labelledby", - recommended: true, + recommended: "strict", url: "https://www.w3.org/WAI/tutorials/forms/labels/" // URL to the documentation page for this rule }, schema: [] @@ -32,9 +32,9 @@ module.exports = { create(context) { return { // visitor functions for different types of nodes - JSXOpeningElement(node) { + JSXOpeningElement(node: TSESTree.JSXOpeningElement) { // if it is not a Toolbar, return - if (elementType(node) !== "Toolbar") { + if (elementType(node as JSXOpeningElement) !== "Toolbar") { return; } @@ -50,5 +50,6 @@ module.exports = { } }; } -}; +}); +export default rule; diff --git a/lib/rules/tooltip-not-recommended.js b/lib/rules/tooltip-not-recommended.ts similarity index 78% rename from lib/rules/tooltip-not-recommended.js rename to lib/rules/tooltip-not-recommended.ts index 6c12a06..b2111c9 100644 --- a/lib/rules/tooltip-not-recommended.js +++ b/lib/rules/tooltip-not-recommended.ts @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - -var elementType = require("jsx-ast-utils").elementType; -const { hasToolTipParent } = require("../util/hasTooltipParent"); +import {ESLintUtils, TSESTree} from '@typescript-eslint/utils'; +import { elementType } from "jsx-ast-utils"; +import { hasToolTipParent } from "../util/hasTooltipParent"; +import { JSXOpeningElement } from 'estree-jsx'; //------------------------------------------------------------------------------ // Rule Definition @@ -12,8 +12,8 @@ const { hasToolTipParent } = require("../util/hasTooltipParent"); // Define an array of allowed component names const allowedComponents = ["MenuItem", "SpinButton"]; -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the lint rule messages: { @@ -22,21 +22,19 @@ module.exports = { type: "suggestion", // `problem`, `suggestion`, or `layout` docs: { description: `Accessibility: Prefer text content or aria over a tooltip for these components ${allowedComponents.join(", ")}`, - recommended: true, - url: null // URL to the documentation page for this rule + recommended: 'strict', }, - fixable: null, // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options }, // 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 - JSXElement(node) { + JSXElement(node: TSESTree.JSXElement) { const openingElement = node.openingElement; // if it is not a listed component, return - if (!allowedComponents.includes(elementType(openingElement))) { + if (!allowedComponents.includes(elementType(openingElement as JSXOpeningElement))) { return; } @@ -50,5 +48,8 @@ module.exports = { } }; } -}; +}); + + +export default rule; \ No newline at end of file diff --git a/tests/lib/rules/accordion-header-needs-labelling.js b/tests/lib/rules/accordion-header-needs-labelling.test.ts similarity index 86% rename from tests/lib/rules/accordion-header-needs-labelling.js rename to tests/lib/rules/accordion-header-needs-labelling.test.ts index bb2310d..93e4304 100644 --- a/tests/lib/rules/accordion-header-needs-labelling.js +++ b/tests/lib/rules/accordion-header-needs-labelling.test.ts @@ -1,31 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const RuleTester = require("eslint").RuleTester; - -const rule = require("../../../lib/rules/accordion-header-needs-labelling"); - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/accordion-header-needs-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("accordion-header-needs-labelling", rule, { +ruleTester.run("accordion-header-needs-labelling", rule as unknown as Rule.RuleModule, { valid: [ "Accordion Header 1", "}>Accordion Header 1", diff --git a/tests/lib/rules/accordion-item-needs-header-and-panel.js b/tests/lib/rules/accordion-item-needs-header-and-panel.test.ts similarity index 81% rename from tests/lib/rules/accordion-item-needs-header-and-panel.js rename to tests/lib/rules/accordion-item-needs-header-and-panel.test.ts index 7037402..2392eda 100644 --- a/tests/lib/rules/accordion-item-needs-header-and-panel.js +++ b/tests/lib/rules/accordion-item-needs-header-and-panel.test.ts @@ -1,30 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/accordion-item-needs-header-and-panel"), - RuleTester = require("eslint").RuleTester; - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/accordion-item-needs-header-and-panel"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("accordion-item-needs-header-and-panel", rule, { +ruleTester.run("accordion-item-needs-header-and-panel", rule as unknown as Rule.RuleModule, { valid: [ `Accordion Header 1
Accordion Panel 1
` ], diff --git a/tests/lib/rules/avatar-needs-name.js b/tests/lib/rules/avatar-needs-name.test.ts similarity index 82% rename from tests/lib/rules/avatar-needs-name.js rename to tests/lib/rules/avatar-needs-name.test.ts index 1769a78..f307c82 100644 --- a/tests/lib/rules/avatar-needs-name.js +++ b/tests/lib/rules/avatar-needs-name.test.ts @@ -1,31 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const RuleTester = require("eslint").RuleTester; - -const rule = require("../../../lib/rules/avatar-needs-name"); - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/avatar-needs-name"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("avatar-needs-name", rule, { +ruleTester.run("avatar-needs-name", rule as unknown as Rule.RuleModule, { valid: [ // give me some code that won't trigger a warning '', diff --git a/tests/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js b/tests/lib/rules/avoid-using-aria-describedby-for-primary-labelling.test.ts similarity index 83% rename from tests/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js rename to tests/lib/rules/avoid-using-aria-describedby-for-primary-labelling.test.ts index 3812535..57c26d0 100644 --- a/tests/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js +++ b/tests/lib/rules/avoid-using-aria-describedby-for-primary-labelling.test.ts @@ -1,30 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/avoid-using-aria-describedby-for-primary-labelling"), - RuleTester = require("eslint").RuleTester; - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/avoid-using-aria-describedby-for-primary-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("avoid-using-aria-describedby-for-primary-labelling", rule, { +ruleTester.run("avoid-using-aria-describedby-for-primary-labelling", rule as unknown as Rule.RuleModule, { valid: [ '<>', diff --git a/tests/lib/rules/badge-needs-accessible-name.js b/tests/lib/rules/badge-needs-accessible-name.test.ts similarity index 85% rename from tests/lib/rules/badge-needs-accessible-name.js rename to tests/lib/rules/badge-needs-accessible-name.test.ts index b47c1ae..fa7db40 100644 --- a/tests/lib/rules/badge-needs-accessible-name.js +++ b/tests/lib/rules/badge-needs-accessible-name.test.ts @@ -1,30 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/badge-needs-accessible-name"), - RuleTester = require("eslint").RuleTester; - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/badge-needs-accessible-name"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("badge-needs-accessible-name", rule, { +ruleTester.run("badge-needs-accessible-name", rule as unknown as Rule.RuleModule, { valid: [ ``, `} />`, @@ -69,4 +58,3 @@ ruleTester.run("badge-needs-accessible-name", rule, { } ] }); - diff --git a/tests/lib/rules/breadcrumb-needs-labelling.js b/tests/lib/rules/breadcrumb-needs-labelling.test.ts similarity index 78% rename from tests/lib/rules/breadcrumb-needs-labelling.js rename to tests/lib/rules/breadcrumb-needs-labelling.test.ts index 5d48c81..802981e 100644 --- a/tests/lib/rules/breadcrumb-needs-labelling.js +++ b/tests/lib/rules/breadcrumb-needs-labelling.test.ts @@ -1,30 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/breadcrumb-needs-labelling"), - RuleTester = require("eslint").RuleTester; - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/breadcrumb-needs-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("breadcrumb-needs-labelling", rule, { +ruleTester.run("breadcrumb-needs-labelling", rule as unknown as Rule.RuleModule, { valid: [ // give me some code that won't trigger a warning '', diff --git a/tests/lib/rules/buttons/compound-button-needs-labelling.js b/tests/lib/rules/buttons/compound-button-needs-labelling.test.ts similarity index 88% rename from tests/lib/rules/buttons/compound-button-needs-labelling.js rename to tests/lib/rules/buttons/compound-button-needs-labelling.test.ts index 022f88a..6c00dca 100644 --- a/tests/lib/rules/buttons/compound-button-needs-labelling.js +++ b/tests/lib/rules/buttons/compound-button-needs-labelling.test.ts @@ -1,21 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ - -const rule = require("../../../../lib/rules/buttons/compound-button-needs-labelling"), - RuleTester = require("eslint").RuleTester; +import { Rule } from "eslint"; +import ruleTester from "../helper/ruleTester"; +import rule from "../../../../lib/rules/buttons/compound-button-needs-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("compound-button-needs-labelling", rule, { +ruleTester.run("compound-button-needs-labelling", rule as unknown as Rule.RuleModule, { valid: [ '}>', "Example", diff --git a/tests/lib/rules/buttons/image-button-missing-aria.js b/tests/lib/rules/buttons/image-button-missing-aria.test.ts similarity index 91% rename from tests/lib/rules/buttons/image-button-missing-aria.js rename to tests/lib/rules/buttons/image-button-missing-aria.test.ts index fa46ffa..eedca93 100644 --- a/tests/lib/rules/buttons/image-button-missing-aria.js +++ b/tests/lib/rules/buttons/image-button-missing-aria.test.ts @@ -1,21 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../../lib/rules/buttons/image-button-missing-aria"), - RuleTester = require("eslint").RuleTester; +import { Rule } from "eslint"; +import ruleTester from "../helper/ruleTester"; +import rule from "../../../../lib/rules/buttons/image-button-missing-aria"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("image-button-missing-aria", rule, { +ruleTester.run("image-button-missing-aria", rule as unknown as Rule.RuleModule, { valid: [ // give me some code that won't trigger a warning '", diff --git a/tests/lib/rules/checkbox-needs-labelling.js b/tests/lib/rules/checkbox-needs-labelling.test.ts similarity index 86% rename from tests/lib/rules/checkbox-needs-labelling.js rename to tests/lib/rules/checkbox-needs-labelling.test.ts index 1af8ed6..90bb791 100644 --- a/tests/lib/rules/checkbox-needs-labelling.js +++ b/tests/lib/rules/checkbox-needs-labelling.test.ts @@ -1,31 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const RuleTester = require("eslint").RuleTester; - -const rule = require("../../../lib/rules/checkbox-needs-labelling"); - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/checkbox-needs-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("checkbox-needs-labelling", rule, { +ruleTester.run("checkbox-needs-labelling", rule as unknown as Rule.RuleModule, { valid: [ "<>", `<>`, diff --git a/tests/lib/rules/combobox-needs-labelling.js b/tests/lib/rules/combobox-needs-labelling.test.ts similarity index 87% rename from tests/lib/rules/combobox-needs-labelling.js rename to tests/lib/rules/combobox-needs-labelling.test.ts index 09aa639..cc64590 100644 --- a/tests/lib/rules/combobox-needs-labelling.js +++ b/tests/lib/rules/combobox-needs-labelling.test.ts @@ -1,30 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/combobox-needs-labelling"), - RuleTester = require("eslint").RuleTester; - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/combobox-needs-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("combobox-needs-labelling", rule, { +ruleTester.run("combobox-needs-labelling", rule as unknown as Rule.RuleModule, { valid: [ // give me some code that won't trigger a warning '', diff --git a/tests/lib/rules/dialogbody-needs-title-content-and-actions.js b/tests/lib/rules/dialogbody-needs-title-content-and-actions.test.ts similarity index 78% rename from tests/lib/rules/dialogbody-needs-title-content-and-actions.js rename to tests/lib/rules/dialogbody-needs-title-content-and-actions.test.ts index e430c57..aaf71e3 100644 --- a/tests/lib/rules/dialogbody-needs-title-content-and-actions.js +++ b/tests/lib/rules/dialogbody-needs-title-content-and-actions.test.ts @@ -1,31 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const RuleTester = require("eslint").RuleTester; - -const rule = require("../../../lib/rules/dialogbody-needs-title-content-and-actions"); - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/dialogbody-needs-title-content-and-actions"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("dialogbody-needs-title-content-and-actions", rule, { +ruleTester.run("dialogbody-needs-title-content-and-actions", rule as any as Rule.RuleModule, { valid: [ ` Dialog title @@ -39,8 +27,7 @@ ruleTester.run("dialogbody-needs-title-content-and-actions", rule, { invalid: [ { - code: - ` + code: ` Test @@ -50,8 +37,7 @@ ruleTester.run("dialogbody-needs-title-content-and-actions", rule, { errors: [{ messageId: "dialogBodyOneTitleOneContentOneFooter" }] }, { - code: - ` + code: ` Dialog title @@ -61,12 +47,11 @@ ruleTester.run("dialogbody-needs-title-content-and-actions", rule, { errors: [{ messageId: "dialogBodyOneTitleOneContentOneFooter" }] }, { - code: - ` + code: ` Dialog title Test `, errors: [{ messageId: "dialogBodyOneTitleOneContentOneFooter" }] - }, + } ] }); diff --git a/tests/lib/rules/dialogsurface-needs-aria.js b/tests/lib/rules/dialogsurface-needs-aria.test.ts similarity index 88% rename from tests/lib/rules/dialogsurface-needs-aria.js rename to tests/lib/rules/dialogsurface-needs-aria.test.ts index ea211e4..902e538 100644 --- a/tests/lib/rules/dialogsurface-needs-aria.js +++ b/tests/lib/rules/dialogsurface-needs-aria.test.ts @@ -1,31 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const RuleTester = require("eslint").RuleTester; - -const rule = require("../../../lib/rules/dialogsurface-needs-aria") - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/dialogsurface-needs-aria"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("dialogsurface-needs-aria", rule, { +ruleTester.run("dialogsurface-needs-aria", rule as any as Rule.RuleModule, { valid: [ `<> My Label @@ -71,8 +59,7 @@ ruleTester.run("dialogsurface-needs-aria", rule, { invalid: [ { - code: - `<> + code: `<> Dialog title @@ -87,8 +74,7 @@ ruleTester.run("dialogsurface-needs-aria", rule, { errors: [{ messageId: "missingAriaOnDialogSurface" }] }, { - code: - `<> + code: `<> diff --git a/tests/lib/rules/dropdown-needs-labelling.js b/tests/lib/rules/dropdown-needs-labelling.test.ts similarity index 89% rename from tests/lib/rules/dropdown-needs-labelling.js rename to tests/lib/rules/dropdown-needs-labelling.test.ts index bf76f54..46a9c7e 100644 --- a/tests/lib/rules/dropdown-needs-labelling.js +++ b/tests/lib/rules/dropdown-needs-labelling.test.ts @@ -1,31 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const RuleTester = require("eslint").RuleTester; - -const rule = require("../../../lib/rules/dropdown-needs-labelling"); - -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { Rule } from "eslint"; +import ruleTester from "./helper/ruleTester"; +import rule from "../../../lib/rules/dropdown-needs-labelling"; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("dropdown-needs-labelling", rule, { +ruleTester.run("dropdown-needs-labelling", rule as unknown as Rule.RuleModule, { valid: [ `<> {options.map((option) => ( ))}`, `<>