From 11b895ee37f01c7c4a7bfecb387b8f6db7664e1c Mon Sep 17 00:00:00 2001 From: Ajay Agarwal Date: Thu, 19 Sep 2024 15:58:53 +0530 Subject: [PATCH 1/6] add config changes for ts migration --- .eslintrc.js | 3 ++- jest.config.js | 9 +++++++++ package.json | 35 +++++++++++++++++++++++++++-------- tsconfig.json | 25 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 jest.config.js create mode 100644 tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index 48e2593..8cab2a2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,5 +24,6 @@ module.exports = { ], rules: { "header/header": [2, "line", [" Copyright (c) Microsoft Corporation.", " Licensed under the MIT License."], 2] - } + }, + ignorePatterns: ["node_modules", "dist/"] }; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..55877bf --- /dev/null +++ b/jest.config.js @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/tests/**/*.test.ts"], + verbose: true +}; diff --git a/package.json b/package.json index f706bff..0faa008 100644 --- a/package.json +++ b/package.json @@ -23,18 +23,27 @@ "url": "https://github.com/microsoft/eslint-plugin-fluentui-jsx-a11y" }, "files": [ - "lib" + "dist" ], - "main": "./lib/index.js", - "exports": "./lib/index.js", + "type": "commonjs", + "exports": { + ".": { + "types": "./dist/lib/index.d.ts", + "default": "./dist/lib/index.js" + }, + "./package.json": "./package.json" + }, + "main": "./dist/lib/index.js", "scripts": { + "build": "tsc", "lint": "npm-run-all \"lint:*\"", "lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"", "lint:js": "eslint .", - "test": "mocha tests --recursive", + "test": "mocha tests --recursive && jest", "lint:docs": "markdownlint **/*.md", "update:eslint-docs": "eslint-doc-generator", - "fix:md": "npm run lint:docs -- --fix" + "fix:md": "npm run lint:docs -- --fix", + "test:new": "jest" }, "dependencies": { "eslint-plugin-header": "^3.1.1", @@ -42,17 +51,27 @@ "requireindex": "^1.2.0" }, "devDependencies": { + "@types/eslint": "=7.2.10", + "@types/estree": "^1.0.5", + "@types/estree-jsx": "^1.0.5", + "@types/jsx-ast-utils": "^3.3.1", + "@types/node": "^22.5.5", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", "chai": "^4.3.8", - "eslint": "^8.38.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.6.0", "eslint-doc-generator": "^1.7.1", "eslint-plugin-eslint-plugin": "^5.0.8", "eslint-plugin-node": "^11.1.0", + "jest": "^29.7.0", "markdownlint": "^0.28.1", "markdownlint-cli": "^0.33.0", "mocha": "^10.0.0", "npm-run-all": "^4.1.5", - "prettier": "2.8.4" + "prettier": "2.8.4", + "ts-jest": "^29.2.5", + "typescript": "^5.6.2" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" @@ -64,4 +83,4 @@ "publishConfig": { "registry": "https://registry.npmjs.org" } -} +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..157afd5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "declaration": true, + "declarationMap": true, + "module": "Node16", + "moduleResolution": "Node16", + "noImplicitReturns": true, + "pretty": true, + "resolveJsonModule": true, + "sourceMap": false, + "lib": ["ES5"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": ".", + "allowJs": true + }, + "include": ["lib"], + "exclude": ["node_modules", "dist"] +} From 97abe08b3bd8da030022d4c00a1d8ff6f9b8e5f5 Mon Sep 17 00:00:00 2001 From: Ajay Agarwal Date: Thu, 19 Sep 2024 16:00:58 +0530 Subject: [PATCH 2/6] run and add build files --- .../buttonBasedComponents.d.ts | 2 + .../buttonBasedComponents.d.ts.map | 1 + .../buttonBasedComponents.js | 7 + .../inputBasedComponents.d.ts | 2 + .../inputBasedComponents.d.ts.map | 1 + .../inputBasedComponents.js | 7 + dist/lib/index.d.ts | 288 ++++++++++++++++++ dist/lib/index.d.ts.map | 1 + dist/lib/index.js | 78 +++++ .../accordion-header-needs-labelling.d.ts | 3 + .../accordion-header-needs-labelling.d.ts.map | 1 + .../rules/accordion-header-needs-labelling.js | 65 ++++ ...accordion-item-needs-header-and-panel.d.ts | 3 + ...rdion-item-needs-header-and-panel.d.ts.map | 1 + .../accordion-item-needs-header-and-panel.js | 40 +++ dist/lib/rules/avatar-needs-name.d.ts | 16 + dist/lib/rules/avatar-needs-name.d.ts.map | 1 + dist/lib/rules/avatar-needs-name.js | 50 +++ ...ria-describedby-for-primary-labelling.d.ts | 3 + ...describedby-for-primary-labelling.d.ts.map | 1 + ...-aria-describedby-for-primary-labelling.js | 62 ++++ .../rules/badge-needs-accessible-name.d.ts | 3 + .../badge-needs-accessible-name.d.ts.map | 1 + dist/lib/rules/badge-needs-accessible-name.js | 98 ++++++ .../lib/rules/breadcrumb-needs-labelling.d.ts | 3 + .../rules/breadcrumb-needs-labelling.d.ts.map | 1 + dist/lib/rules/breadcrumb-needs-labelling.js | 49 +++ .../compound-button-needs-labelling.d.ts | 16 + .../compound-button-needs-labelling.d.ts.map | 1 + .../compound-button-needs-labelling.js | 56 ++++ .../buttons/image-button-missing-aria.d.ts | 16 + .../image-button-missing-aria.d.ts.map | 1 + .../buttons/image-button-missing-aria.js | 67 ++++ dist/lib/rules/buttons/no-empty-buttons.d.ts | 16 + .../rules/buttons/no-empty-buttons.d.ts.map | 1 + dist/lib/rules/buttons/no-empty-buttons.js | 60 ++++ dist/lib/rules/checkbox-needs-labelling.d.ts | 16 + .../rules/checkbox-needs-labelling.d.ts.map | 1 + dist/lib/rules/checkbox-needs-labelling.js | 53 ++++ dist/lib/rules/combobox-needs-labelling.d.ts | 3 + .../rules/combobox-needs-labelling.d.ts.map | 1 + dist/lib/rules/combobox-needs-labelling.js | 53 ++++ ...gbody-needs-title-content-and-actions.d.ts | 3 + ...y-needs-title-content-and-actions.d.ts.map | 1 + ...logbody-needs-title-content-and-actions.js | 41 +++ dist/lib/rules/dialogsurface-needs-aria.d.ts | 3 + .../rules/dialogsurface-needs-aria.d.ts.map | 1 + dist/lib/rules/dialogsurface-needs-aria.js | 59 ++++ dist/lib/rules/dropdown-needs-labelling.d.ts | 16 + .../rules/dropdown-needs-labelling.d.ts.map | 1 + dist/lib/rules/dropdown-needs-labelling.js | 53 ++++ dist/lib/rules/image-link-missing-aria.d.ts | 3 + .../rules/image-link-missing-aria.d.ts.map | 1 + dist/lib/rules/image-link-missing-aria.js | 69 +++++ ...ut-components-require-accessible-name.d.ts | 16 + ...omponents-require-accessible-name.d.ts.map | 1 + ...nput-components-require-accessible-name.js | 49 +++ dist/lib/rules/menu-item-needs-labelling.d.ts | 3 + .../rules/menu-item-needs-labelling.d.ts.map | 1 + dist/lib/rules/menu-item-needs-labelling.js | 54 ++++ dist/lib/rules/no-empty-components.d.ts | 3 + dist/lib/rules/no-empty-components.d.ts.map | 1 + dist/lib/rules/no-empty-components.js | 47 +++ .../prefer-aria-over-title-attribute.d.ts | 3 + .../prefer-aria-over-title-attribute.d.ts.map | 1 + .../rules/prefer-aria-over-title-attribute.js | 77 +++++ .../rules/progressbar-needs-labelling.d.ts | 16 + .../progressbar-needs-labelling.d.ts.map | 1 + dist/lib/rules/progressbar-needs-labelling.js | 61 ++++ .../lib/rules/radio-button-missing-label.d.ts | 16 + .../rules/radio-button-missing-label.d.ts.map | 1 + dist/lib/rules/radio-button-missing-label.js | 54 ++++ dist/lib/rules/radiogroup-missing-label.d.ts | 16 + .../rules/radiogroup-missing-label.d.ts.map | 1 + dist/lib/rules/radiogroup-missing-label.js | 54 ++++ .../rules/spin-button-needs-labelling.d.ts | 16 + .../spin-button-needs-labelling.d.ts.map | 1 + dist/lib/rules/spin-button-needs-labelling.js | 50 +++ .../spin-button-unrecommended-labelling.d.ts | 16 + ...in-button-unrecommended-labelling.d.ts.map | 1 + .../spin-button-unrecommended-labelling.js | 45 +++ dist/lib/rules/spinner-needs-labelling.d.ts | 16 + .../rules/spinner-needs-labelling.d.ts.map | 1 + dist/lib/rules/spinner-needs-labelling.js | 47 +++ dist/lib/rules/switch-needs-labelling.d.ts | 16 + .../lib/rules/switch-needs-labelling.d.ts.map | 1 + dist/lib/rules/switch-needs-labelling.js | 52 ++++ dist/lib/rules/toolbar-missing-aria.d.ts | 3 + dist/lib/rules/toolbar-missing-aria.d.ts.map | 1 + dist/lib/rules/toolbar-missing-aria.js | 46 +++ dist/lib/rules/tooltip-not-recommended.d.ts | 3 + .../rules/tooltip-not-recommended.d.ts.map | 1 + dist/lib/rules/tooltip-not-recommended.js | 47 +++ dist/lib/util/flattenChildren.d.ts | 2 + dist/lib/util/flattenChildren.d.ts.map | 1 + dist/lib/util/flattenChildren.js | 19 ++ dist/lib/util/hasFieldParent.d.ts | 2 + dist/lib/util/hasFieldParent.d.ts.map | 1 + dist/lib/util/hasFieldParent.js | 23 ++ dist/lib/util/hasNonEmptyProp.d.ts | 8 + dist/lib/util/hasNonEmptyProp.d.ts.map | 1 + dist/lib/util/hasNonEmptyProp.js | 27 ++ dist/lib/util/hasTextContentChild.d.ts | 7 + dist/lib/util/hasTextContentChild.d.ts.map | 1 + dist/lib/util/hasTextContentChild.js | 21 ++ dist/lib/util/hasTooltipParent.d.ts | 2 + dist/lib/util/hasTooltipParent.d.ts.map | 1 + dist/lib/util/hasTooltipParent.js | 23 ++ dist/lib/util/labelUtils.d.ts | 72 +++++ dist/lib/util/labelUtils.d.ts.map | 1 + dist/lib/util/labelUtils.js | 136 +++++++++ 111 files changed, 2587 insertions(+) create mode 100644 dist/lib/applicableComponents/buttonBasedComponents.d.ts create mode 100644 dist/lib/applicableComponents/buttonBasedComponents.d.ts.map create mode 100644 dist/lib/applicableComponents/buttonBasedComponents.js create mode 100644 dist/lib/applicableComponents/inputBasedComponents.d.ts create mode 100644 dist/lib/applicableComponents/inputBasedComponents.d.ts.map create mode 100644 dist/lib/applicableComponents/inputBasedComponents.js create mode 100644 dist/lib/index.d.ts create mode 100644 dist/lib/index.d.ts.map create mode 100644 dist/lib/index.js create mode 100644 dist/lib/rules/accordion-header-needs-labelling.d.ts create mode 100644 dist/lib/rules/accordion-header-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/accordion-header-needs-labelling.js create mode 100644 dist/lib/rules/accordion-item-needs-header-and-panel.d.ts create mode 100644 dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map create mode 100644 dist/lib/rules/accordion-item-needs-header-and-panel.js create mode 100644 dist/lib/rules/avatar-needs-name.d.ts create mode 100644 dist/lib/rules/avatar-needs-name.d.ts.map create mode 100644 dist/lib/rules/avatar-needs-name.js create mode 100644 dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts create mode 100644 dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map create mode 100644 dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js create mode 100644 dist/lib/rules/badge-needs-accessible-name.d.ts create mode 100644 dist/lib/rules/badge-needs-accessible-name.d.ts.map create mode 100644 dist/lib/rules/badge-needs-accessible-name.js create mode 100644 dist/lib/rules/breadcrumb-needs-labelling.d.ts create mode 100644 dist/lib/rules/breadcrumb-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/breadcrumb-needs-labelling.js create mode 100644 dist/lib/rules/buttons/compound-button-needs-labelling.d.ts create mode 100644 dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/buttons/compound-button-needs-labelling.js create mode 100644 dist/lib/rules/buttons/image-button-missing-aria.d.ts create mode 100644 dist/lib/rules/buttons/image-button-missing-aria.d.ts.map create mode 100644 dist/lib/rules/buttons/image-button-missing-aria.js create mode 100644 dist/lib/rules/buttons/no-empty-buttons.d.ts create mode 100644 dist/lib/rules/buttons/no-empty-buttons.d.ts.map create mode 100644 dist/lib/rules/buttons/no-empty-buttons.js create mode 100644 dist/lib/rules/checkbox-needs-labelling.d.ts create mode 100644 dist/lib/rules/checkbox-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/checkbox-needs-labelling.js create mode 100644 dist/lib/rules/combobox-needs-labelling.d.ts create mode 100644 dist/lib/rules/combobox-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/combobox-needs-labelling.js create mode 100644 dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts create mode 100644 dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map create mode 100644 dist/lib/rules/dialogbody-needs-title-content-and-actions.js create mode 100644 dist/lib/rules/dialogsurface-needs-aria.d.ts create mode 100644 dist/lib/rules/dialogsurface-needs-aria.d.ts.map create mode 100644 dist/lib/rules/dialogsurface-needs-aria.js create mode 100644 dist/lib/rules/dropdown-needs-labelling.d.ts create mode 100644 dist/lib/rules/dropdown-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/dropdown-needs-labelling.js create mode 100644 dist/lib/rules/image-link-missing-aria.d.ts create mode 100644 dist/lib/rules/image-link-missing-aria.d.ts.map create mode 100644 dist/lib/rules/image-link-missing-aria.js create mode 100644 dist/lib/rules/input-components-require-accessible-name.d.ts create mode 100644 dist/lib/rules/input-components-require-accessible-name.d.ts.map create mode 100644 dist/lib/rules/input-components-require-accessible-name.js create mode 100644 dist/lib/rules/menu-item-needs-labelling.d.ts create mode 100644 dist/lib/rules/menu-item-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/menu-item-needs-labelling.js create mode 100644 dist/lib/rules/no-empty-components.d.ts create mode 100644 dist/lib/rules/no-empty-components.d.ts.map create mode 100644 dist/lib/rules/no-empty-components.js create mode 100644 dist/lib/rules/prefer-aria-over-title-attribute.d.ts create mode 100644 dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map create mode 100644 dist/lib/rules/prefer-aria-over-title-attribute.js create mode 100644 dist/lib/rules/progressbar-needs-labelling.d.ts create mode 100644 dist/lib/rules/progressbar-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/progressbar-needs-labelling.js create mode 100644 dist/lib/rules/radio-button-missing-label.d.ts create mode 100644 dist/lib/rules/radio-button-missing-label.d.ts.map create mode 100644 dist/lib/rules/radio-button-missing-label.js create mode 100644 dist/lib/rules/radiogroup-missing-label.d.ts create mode 100644 dist/lib/rules/radiogroup-missing-label.d.ts.map create mode 100644 dist/lib/rules/radiogroup-missing-label.js create mode 100644 dist/lib/rules/spin-button-needs-labelling.d.ts create mode 100644 dist/lib/rules/spin-button-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/spin-button-needs-labelling.js create mode 100644 dist/lib/rules/spin-button-unrecommended-labelling.d.ts create mode 100644 dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map create mode 100644 dist/lib/rules/spin-button-unrecommended-labelling.js create mode 100644 dist/lib/rules/spinner-needs-labelling.d.ts create mode 100644 dist/lib/rules/spinner-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/spinner-needs-labelling.js create mode 100644 dist/lib/rules/switch-needs-labelling.d.ts create mode 100644 dist/lib/rules/switch-needs-labelling.d.ts.map create mode 100644 dist/lib/rules/switch-needs-labelling.js create mode 100644 dist/lib/rules/toolbar-missing-aria.d.ts create mode 100644 dist/lib/rules/toolbar-missing-aria.d.ts.map create mode 100644 dist/lib/rules/toolbar-missing-aria.js create mode 100644 dist/lib/rules/tooltip-not-recommended.d.ts create mode 100644 dist/lib/rules/tooltip-not-recommended.d.ts.map create mode 100644 dist/lib/rules/tooltip-not-recommended.js create mode 100644 dist/lib/util/flattenChildren.d.ts create mode 100644 dist/lib/util/flattenChildren.d.ts.map create mode 100644 dist/lib/util/flattenChildren.js create mode 100644 dist/lib/util/hasFieldParent.d.ts create mode 100644 dist/lib/util/hasFieldParent.d.ts.map create mode 100644 dist/lib/util/hasFieldParent.js create mode 100644 dist/lib/util/hasNonEmptyProp.d.ts create mode 100644 dist/lib/util/hasNonEmptyProp.d.ts.map create mode 100644 dist/lib/util/hasNonEmptyProp.js create mode 100644 dist/lib/util/hasTextContentChild.d.ts create mode 100644 dist/lib/util/hasTextContentChild.d.ts.map create mode 100644 dist/lib/util/hasTextContentChild.js create mode 100644 dist/lib/util/hasTooltipParent.d.ts create mode 100644 dist/lib/util/hasTooltipParent.d.ts.map create mode 100644 dist/lib/util/hasTooltipParent.js create mode 100644 dist/lib/util/labelUtils.d.ts create mode 100644 dist/lib/util/labelUtils.d.ts.map create mode 100644 dist/lib/util/labelUtils.js diff --git a/dist/lib/applicableComponents/buttonBasedComponents.d.ts b/dist/lib/applicableComponents/buttonBasedComponents.d.ts new file mode 100644 index 0000000..3f42888 --- /dev/null +++ b/dist/lib/applicableComponents/buttonBasedComponents.d.ts @@ -0,0 +1,2 @@ +export const applicableComponents: string[]; +//# sourceMappingURL=buttonBasedComponents.d.ts.map \ No newline at end of file diff --git a/dist/lib/applicableComponents/buttonBasedComponents.d.ts.map b/dist/lib/applicableComponents/buttonBasedComponents.d.ts.map new file mode 100644 index 0000000..66e5a16 --- /dev/null +++ b/dist/lib/applicableComponents/buttonBasedComponents.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"buttonBasedComponents.d.ts","sourceRoot":"","sources":["../../../lib/applicableComponents/buttonBasedComponents.js"],"names":[],"mappings":"AAGA,4CAA0E"} \ No newline at end of file diff --git a/dist/lib/applicableComponents/buttonBasedComponents.js b/dist/lib/applicableComponents/buttonBasedComponents.js new file mode 100644 index 0000000..80184ee --- /dev/null +++ b/dist/lib/applicableComponents/buttonBasedComponents.js @@ -0,0 +1,7 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +const applicableComponents = ["Button", "ToggleButton", "CompoundButton"]; +module.exports = { + applicableComponents +}; diff --git a/dist/lib/applicableComponents/inputBasedComponents.d.ts b/dist/lib/applicableComponents/inputBasedComponents.d.ts new file mode 100644 index 0000000..2677eb3 --- /dev/null +++ b/dist/lib/applicableComponents/inputBasedComponents.d.ts @@ -0,0 +1,2 @@ +export const applicableComponents: string[]; +//# sourceMappingURL=inputBasedComponents.d.ts.map \ No newline at end of file diff --git a/dist/lib/applicableComponents/inputBasedComponents.d.ts.map b/dist/lib/applicableComponents/inputBasedComponents.d.ts.map new file mode 100644 index 0000000..a9dbd2a --- /dev/null +++ b/dist/lib/applicableComponents/inputBasedComponents.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"inputBasedComponents.d.ts","sourceRoot":"","sources":["../../../lib/applicableComponents/inputBasedComponents.js"],"names":[],"mappings":"AAGA,4CAA6H"} \ No newline at end of file diff --git a/dist/lib/applicableComponents/inputBasedComponents.js b/dist/lib/applicableComponents/inputBasedComponents.js new file mode 100644 index 0000000..3009f44 --- /dev/null +++ b/dist/lib/applicableComponents/inputBasedComponents.js @@ -0,0 +1,7 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +const applicableComponents = ["Input", "Slider", "DatePicker", "Textarea", "TextField", "TimePicker", "SearchBox", "Select"]; +module.exports = { + applicableComponents +}; diff --git a/dist/lib/index.d.ts b/dist/lib/index.d.ts new file mode 100644 index 0000000..7e06653 --- /dev/null +++ b/dist/lib/index.d.ts @@ -0,0 +1,288 @@ +export let rules: { + "checkbox-needs-labelling": { + meta: { + messages: { + noUnlabelledCheckbox: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "image-button-missing-aria": { + meta: { + messages: { + missingAriaLabel: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXElement(node: any): void; + }; + }; + "image-link-missing-aria": import("eslint").Rule.RuleModule; + "input-components-require-accessible-name": { + meta: { + messages: { + missingLabelOnInput: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "menu-item-needs-labelling": import("eslint").Rule.RuleModule; + "switch-needs-labelling": { + meta: { + messages: { + noUnlabelledSwitch: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "toolbar-missing-aria": import("eslint").Rule.RuleModule; + "combobox-needs-labelling": import("eslint").Rule.RuleModule; + "no-empty-components": import("eslint").Rule.RuleModule; + "accordion-header-needs-labelling": import("eslint").Rule.RuleModule; + "accordion-item-needs-header-and-panel": import("eslint").Rule.RuleModule; + "compound-button-needs-labelling": { + meta: { + messages: { + missingAriaLabel: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXElement(node: any): void; + }; + }; + "no-empty-buttons": { + meta: { + messages: { + noEmptyButtons: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXElement(node: any): any; + }; + }; + "spin-button-needs-labelling": { + meta: { + messages: { + noUnlabelledSpinButton: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "spin-button-unrecommended-labelling": { + meta: { + messages: { + unRecommendedlabellingSpinButton: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "breadcrumb-needs-labelling": import("eslint").Rule.RuleModule; + "dropdown-needs-labelling": { + meta: { + messages: { + missingLabelOrAriaLabeledByInDropdown: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: null; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "tooltip-not-recommended": import("eslint").Rule.RuleModule; + "avatar-needs-name": { + meta: { + messages: { + missingAriaLabel: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "radio-button-missing-label": { + meta: { + messages: { + noUnlabeledRadioButton: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "radiogroup-missing-label": { + meta: { + messages: { + noUnlabeledRadioGroup: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "prefer-aria-over-title-attribute": import("eslint").Rule.RuleModule; + "dialogbody-needs-title-content-and-actions": import("eslint").Rule.RuleModule; + "dialogsurface-needs-aria": import("eslint").Rule.RuleModule; + "spinner-needs-labelling": { + meta: { + messages: { + noUnlabelledSpinner: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; + "badge-needs-accessible-name": import("eslint").Rule.RuleModule; + "progressbar-needs-labelling": { + meta: { + messages: { + noUnlabelledProgressbar: string; + }; + type: string; + docs: { + description: string; + recommended: boolean; + url: string; + }; + schema: never[]; + }; + create(context: any): { + JSXOpeningElement(node: any): void; + }; + }; +}; +export namespace configs { + namespace recommended { + let rules_1: { + "@microsoft/fluentui-jsx-a11y/checkbox-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/image-link-missing-aria": string; + "@microsoft/fluentui-jsx-a11y/input-components-require-accessible-name": string; + "@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/switch-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/image-button-missing-aria": string; + "@microsoft/fluentui-jsx-a11y/toolbar-missing-aria": string; + "@microsoft/fluentui-jsx-a11y/combobox-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/no-empty-components": string; + "@microsoft/fluentui-jsx-a11y/accordion-header-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/accordion-item-needs-header-and-panel": string; + "@microsoft/fluentui-jsx-a11y/compound-button-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/no-empty-buttons": string; + "@microsoft/fluentui-jsx-a11y/spin-button-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/spin-button-unrecommended-labelling": string; + "@microsoft/fluentui-jsx-a11y/breadcrumb-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/dropdown-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/tooltip-not-recommended": string; + "@microsoft/fluentui-jsx-a11y/avatar-needs-name": string; + "@microsoft/fluentui-jsx-a11y/radio-button-missing-label": string; + "@microsoft/fluentui-jsx-a11y/radiogroup-missing-label": string; + "@microsoft/fluentui-jsx-a11y/prefer-aria-over-title-attribute": string; + "@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": string; + "@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": string; + "@microsoft/fluentui-jsx-a11y/spinner-needs-labelling": string; + "@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": string; + }; + export { rules_1 as rules }; + } +} +export let processors: {}; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/lib/index.d.ts.map b/dist/lib/index.d.ts.map new file mode 100644 index 0000000..bc39b0a --- /dev/null +++ b/dist/lib/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/index.js"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/index.js b/dist/lib/index.js new file mode 100644 index 0000000..b969c49 --- /dev/null +++ b/dist/lib/index.js @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +console.log("Loading my-eslint-plugin"); +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +// Plugin Definition +//------------------------------------------------------------------------------ +// 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"), + "image-link-missing-aria": require("./rules/image-link-missing-aria"), + "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"), + "prefer-aria-over-title-attribute": require("./rules/prefer-aria-over-title-attribute"), + "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") + }, + configs: { + recommended: { + rules: { + "@microsoft/fluentui-jsx-a11y/checkbox-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/image-link-missing-aria": "error", + "@microsoft/fluentui-jsx-a11y/input-components-require-accessible-name": "error", + "@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/switch-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/image-button-missing-aria": "error", + "@microsoft/fluentui-jsx-a11y/toolbar-missing-aria": "error", + "@microsoft/fluentui-jsx-a11y/combobox-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/no-empty-components": "error", + "@microsoft/fluentui-jsx-a11y/accordion-header-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/accordion-item-needs-header-and-panel": "error", + "@microsoft/fluentui-jsx-a11y/compound-button-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/no-empty-buttons": "error", + "@microsoft/fluentui-jsx-a11y/spin-button-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/spin-button-unrecommended-labelling": "error", + "@microsoft/fluentui-jsx-a11y/breadcrumb-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/dropdown-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/tooltip-not-recommended": "error", + "@microsoft/fluentui-jsx-a11y/avatar-needs-name": "error", + "@microsoft/fluentui-jsx-a11y/radio-button-missing-label": "error", + "@microsoft/fluentui-jsx-a11y/radiogroup-missing-label": "error", + "@microsoft/fluentui-jsx-a11y/prefer-aria-over-title-attribute": "warn", + "@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": "error", + "@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": "error", + "@microsoft/fluentui-jsx-a11y/spinner-needs-labelling": "error", + "@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": "error" + } + } + } +}; +// import processors +module.exports.processors = { +// add your processors here +}; diff --git a/dist/lib/rules/accordion-header-needs-labelling.d.ts b/dist/lib/rules/accordion-header-needs-labelling.d.ts new file mode 100644 index 0000000..8e7a695 --- /dev/null +++ b/dist/lib/rules/accordion-header-needs-labelling.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=accordion-header-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/accordion-header-needs-labelling.d.ts.map b/dist/lib/rules/accordion-header-needs-labelling.d.ts.map new file mode 100644 index 0000000..9c1a3bf --- /dev/null +++ b/dist/lib/rules/accordion-header-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"accordion-header-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/accordion-header-needs-labelling.js"],"names":[],"mappings":"wBAgBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/accordion-header-needs-labelling.js b/dist/lib/rules/accordion-header-needs-labelling.js new file mode 100644 index 0000000..f3b0a88 --- /dev/null +++ b/dist/lib/rules/accordion-header-needs-labelling.js @@ -0,0 +1,65 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + messages: { + missingAriaLabel: "Accessibility: the accordion header must have an accessible name" + }, + type: "problem", // `problem`, `suggestion`, or `layout` + 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 + }, + 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) { + const openingElement = node.openingElement; + // if it is not a AccordionHeader, return + if (elementType(openingElement) !== "AccordionHeader") { + return; + } + // if it has text content, return + if (hasTextContentChild(node)) { + return; + } + // if it is not an icon button, return + if (!hasProp(openingElement.attributes, "icon") && !hasProp(openingElement.attributes, "expandIcon")) { + return; + } + // if it has a tooltip parent, return + if (hasToolTipParent(context)) { + return; + } + // the button has an associated label + if (hasAssociatedLabelViaAriaLabelledBy(openingElement, context)) { + return; + } + const hasAccessibleLabelling = hasNonEmptyProp(openingElement.attributes, "title") || hasNonEmptyProp(openingElement.attributes, "aria-label"); + // if it has no accessible name, report error + if (!hasAccessibleLabelling) { + context.report({ + node, + messageId: `missingAriaLabel` + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts new file mode 100644 index 0000000..21c3f73 --- /dev/null +++ b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=accordion-item-needs-header-and-panel.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map new file mode 100644 index 0000000..e11b962 --- /dev/null +++ b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"accordion-item-needs-header-and-panel.d.ts","sourceRoot":"","sources":["../../../lib/rules/accordion-item-needs-header-and-panel.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/accordion-item-needs-header-and-panel.js b/dist/lib/rules/accordion-item-needs-header-and-panel.js new file mode 100644 index 0000000..6dc0f8d --- /dev/null +++ b/dist/lib/rules/accordion-item-needs-header-and-panel.js @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + messages: { + accordionItemOneHeaderOnePanel: "ensure AccordionItem has exactly one header and one panel" + }, + type: "problem", // `problem`, `suggestion`, or `layout` + docs: { + description: "An AccordionItem needs exactly one header and one panel", + recommended: true, + 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") { + return; + } + const children = node.parent.children.filter(child => child.type === "JSXElement"); + const hasOneHeader = children.filter(child => child.openingElement.name.name === "AccordionHeader").length === 1; + const hasOnePanel = children.filter(child => child.openingElement.name.name === "AccordionPanel").length === 1; + if (!hasOneHeader || !hasOnePanel || children.length !== 2) { + context.report({ + node, + messageId: "accordionItemOneHeaderOnePanel" + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/avatar-needs-name.d.ts b/dist/lib/rules/avatar-needs-name.d.ts new file mode 100644 index 0000000..a89b58b --- /dev/null +++ b/dist/lib/rules/avatar-needs-name.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let missingAriaLabel: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=avatar-needs-name.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/avatar-needs-name.d.ts.map b/dist/lib/rules/avatar-needs-name.d.ts.map new file mode 100644 index 0000000..80495d5 --- /dev/null +++ b/dist/lib/rules/avatar-needs-name.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"avatar-needs-name.d.ts","sourceRoot":"","sources":["../../../lib/rules/avatar-needs-name.js"],"names":[],"mappings":";;;;;;;;;;;;AA+BI;;EAyBC"} \ No newline at end of file diff --git a/dist/lib/rules/avatar-needs-name.js b/dist/lib/rules/avatar-needs-name.js new file mode 100644 index 0000000..fc133e3 --- /dev/null +++ b/dist/lib/rules/avatar-needs-name.js @@ -0,0 +1,50 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + missingAriaLabel: "Accessibility: Avatar must have an accessible name" + }, + // "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: { + // DONE + description: "Accessibility: Avatar must have an accessible labelling: name, aria-label, aria-labelledby", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not an Avatar, return + if (elementType(node) !== "Avatar") { + return; + } + // if the Avatar has a name, aria-label or aria-labelledby, return + if (hasNonEmptyProp(node.attributes, "name") || + hasNonEmptyProp(node.attributes, "aria-label") || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + // no aria + context.report({ + node, + messageId: `missingAriaLabel` + }); + } + }; + } +}; diff --git a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts new file mode 100644 index 0000000..f90a714 --- /dev/null +++ b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=avoid-using-aria-describedby-for-primary-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map new file mode 100644 index 0000000..da2dc48 --- /dev/null +++ b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"avoid-using-aria-describedby-for-primary-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/avoid-using-aria-describedby-for-primary-labelling.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js new file mode 100644 index 0000000..702d7e5 --- /dev/null +++ b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.js @@ -0,0 +1,62 @@ +// 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 { 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + messages: { + noAriaDescribedbyAsLabel: "Accessibility: aria-describedby provides additional context and is not meant for primary labeling." + }, + 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 + }, + fixable: null, // Or `code` or `whitespace` + schema: [] // Add a schema if the rule has options + }, + create(context) { + return { + JSXElement(node) { + const openingElement = node.openingElement; + if (buttonComponents.includes(elementType(openingElement)) && // 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 + !hasNonEmptyProp(openingElement.attributes, "aria-label") && // Doesn't have an aria-label + !hasAssociatedLabelViaAriaLabelledBy(openingElement, context) && // Doesn't have aria-labelledby + hasAssociatedLabelViaAriaDescribedby(openingElement, context) // But it does have aria-describedby + ) { + context.report({ + node, + messageId: "noAriaDescribedbyAsLabel" + }); + } + if (inputComponents.includes(elementType(openingElement)) && // 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 + !hasAssociatedLabelViaAriaLabelledBy(openingElement, context) && // Doesn't have aria-labelledby + hasAssociatedLabelViaAriaDescribedby(openingElement, context) // But it does have aria-describedby + ) { + context.report({ + node, + messageId: "noAriaDescribedbyAsLabel" + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/badge-needs-accessible-name.d.ts b/dist/lib/rules/badge-needs-accessible-name.d.ts new file mode 100644 index 0000000..c06ed2d --- /dev/null +++ b/dist/lib/rules/badge-needs-accessible-name.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=badge-needs-accessible-name.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/badge-needs-accessible-name.d.ts.map b/dist/lib/rules/badge-needs-accessible-name.d.ts.map new file mode 100644 index 0000000..0d08843 --- /dev/null +++ b/dist/lib/rules/badge-needs-accessible-name.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"badge-needs-accessible-name.d.ts","sourceRoot":"","sources":["../../../lib/rules/badge-needs-accessible-name.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/badge-needs-accessible-name.js b/dist/lib/rules/badge-needs-accessible-name.js new file mode 100644 index 0000000..06b1dad --- /dev/null +++ b/dist/lib/rules/badge-needs-accessible-name.js @@ -0,0 +1,98 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + badgeNeedsAccessibleName: "Badge: needs accessible name. Add text content or a labelled image.", + colourOnlyBadgesNeedAttributes: 'Color-only must have role="img" and an aria-label attribute.', + badgeIconNeedsLabelling: "The icon inside must have an aria-label attribute." + }, + type: "problem", // `problem`, `suggestion`, or `layout` + docs: { + description: "", + recommended: false, + url: "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/img_role" // URL to the documentation page for this rule + }, + fixable: "code", // 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) { + const openingElement = node.openingElement; + // If it's not a Badge component, return early + if (elementType(openingElement) !== "Badge") { + return; + } + const hasTextContent = hasTextContentChild(node); + // Check if Badge has text content and return early if it does + if (hasTextContent) { + return; + } + // Check if Badge has an icon + const hasIconProp = hasProp(openingElement.attributes, "icon"); + if (hasIconProp) { + const iconProp = getProp(openingElement.attributes, "icon"); + if (iconProp) { + const iconElement = iconProp.value.expression; + // Check if the icon has an aria-label + const ariaLabelAttr = hasProp(iconElement.openingElement.attributes, "aria-label"); + // Report an error if aria-label is missing + if (!ariaLabelAttr) { + context.report({ + node, + messageId: "badgeIconNeedsLabelling", + fix(fixer) { + const ariaLabelFix = fixer.insertTextAfter(iconElement.openingElement.name, ' aria-label=""'); + return ariaLabelFix; + } + }); + } + } + } + // Simplified logic to check for a color-only Badge (no icon, no text) + const hasColorProp = hasProp(openingElement.attributes, "color"); + const hasRole = getPropValue(getProp(openingElement.attributes, "role")) === "img"; + const hasAriaLabel = hasProp(openingElement.attributes, "aria-label"); + // If it's color-only, ensure it has role="img" and aria-label + if (!hasIconProp && !(hasRole && hasAriaLabel)) { + if (hasColorProp) { + context.report({ + node, + messageId: "colourOnlyBadgesNeedAttributes", + fix(fixer) { + const fixes = []; + // Fix role by adding role="img" + if (!hasRole) { + fixes.push(fixer.insertTextAfter(openingElement.name, ' role="img"')); + } + // Fix aria-label by adding aria-label="" + if (!hasAriaLabel) { + fixes.push(fixer.insertTextAfter(openingElement.name, ' aria-label=""')); + } + return fixes; + } + }); + } + else { + context.report({ + node, + messageId: "badgeNeedsAccessibleName" + }); + } + } + } + }; + } +}; diff --git a/dist/lib/rules/breadcrumb-needs-labelling.d.ts b/dist/lib/rules/breadcrumb-needs-labelling.d.ts new file mode 100644 index 0000000..6beb885 --- /dev/null +++ b/dist/lib/rules/breadcrumb-needs-labelling.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=breadcrumb-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/breadcrumb-needs-labelling.d.ts.map b/dist/lib/rules/breadcrumb-needs-labelling.d.ts.map new file mode 100644 index 0000000..1296153 --- /dev/null +++ b/dist/lib/rules/breadcrumb-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"breadcrumb-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/breadcrumb-needs-labelling.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/breadcrumb-needs-labelling.js b/dist/lib/rules/breadcrumb-needs-labelling.js new file mode 100644 index 0000000..d3b50aa --- /dev/null +++ b/dist/lib/rules/breadcrumb-needs-labelling.js @@ -0,0 +1,49 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledBreadcrumb: "Accessibility: Breadcrumb must have an accessible label" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + docs: { + description: "All interactive elements must have an accessible name", + 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) { + // if it is not a Breadcrumb, return + if (elementType(node) !== "Breadcrumb") { + return; + } + // if the Breadcrumb has a label, if the Breadcrumb has an associated label, return + if (hasNonEmptyProp(node.attributes, "aria-label") || //aria-label + hasAssociatedLabelViaAriaLabelledBy(node, context) // aria-labelledby + ) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledBreadcrumb` + }); + } + }; + } +}; diff --git a/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts new file mode 100644 index 0000000..19367c6 --- /dev/null +++ b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let missingAriaLabel: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXElement(node: any): void; +}; +//# sourceMappingURL=compound-button-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map new file mode 100644 index 0000000..eebf6de --- /dev/null +++ b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"compound-button-needs-labelling.d.ts","sourceRoot":"","sources":["../../../../lib/rules/buttons/compound-button-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AAiCI;;EAiCC"} \ No newline at end of file diff --git a/dist/lib/rules/buttons/compound-button-needs-labelling.js b/dist/lib/rules/buttons/compound-button-needs-labelling.js new file mode 100644 index 0000000..1706449 --- /dev/null +++ b/dist/lib/rules/buttons/compound-button-needs-labelling.js @@ -0,0 +1,56 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + missingAriaLabel: "Accessibility: Compound buttons must have an accessible name" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Compound buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXElement(node) { + const openingElement = node.openingElement; + // if it is not a Compound button, return + if (elementType(openingElement) !== "CompoundButton") { + return; + } + // if it has a tooltip parent Or has text content Or has an associated label or has secondaryContent, return + if (hasToolTipParent(context) || + hasTextContentChild(node) || + hasAssociatedLabelViaAriaLabelledBy(openingElement, context) || + hasNonEmptyProp(openingElement.attributes, "secondaryContent")) { + return; + } + const hasAccessibleLabelling = hasNonEmptyProp(openingElement.attributes, "title") || hasNonEmptyProp(openingElement.attributes, "aria-label"); + // if it has no accessible name, report error + if (!hasAccessibleLabelling) { + context.report({ + node, + messageId: `missingAriaLabel` + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/buttons/image-button-missing-aria.d.ts b/dist/lib/rules/buttons/image-button-missing-aria.d.ts new file mode 100644 index 0000000..5bbc38e --- /dev/null +++ b/dist/lib/rules/buttons/image-button-missing-aria.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let missingAriaLabel: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXElement(node: any): void; +}; +//# sourceMappingURL=image-button-missing-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/buttons/image-button-missing-aria.d.ts.map b/dist/lib/rules/buttons/image-button-missing-aria.d.ts.map new file mode 100644 index 0000000..f19dbbe --- /dev/null +++ b/dist/lib/rules/buttons/image-button-missing-aria.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"image-button-missing-aria.d.ts","sourceRoot":"","sources":["../../../../lib/rules/buttons/image-button-missing-aria.js"],"names":[],"mappings":";;;;;;;;;;;;AAmCI;;EA2CC"} \ No newline at end of file diff --git a/dist/lib/rules/buttons/image-button-missing-aria.js b/dist/lib/rules/buttons/image-button-missing-aria.js new file mode 100644 index 0000000..4cfac1a --- /dev/null +++ b/dist/lib/rules/buttons/image-button-missing-aria.js @@ -0,0 +1,67 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + missingAriaLabel: "Accessibility: Image buttons must have an accessible name" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Image buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXElement(node) { + const openingElement = node.openingElement; + // if it is not a button, return + if (!applicableComponents.includes(elementType(openingElement))) { + return; + } + // if it is not an icon button, return + if (!hasProp(openingElement.attributes, "icon")) { + return; + } + // if it has a tooltip parent, return + if (hasToolTipParent(context)) { + return; + } + // if it has text content, return + if (hasTextContentChild(node)) { + return; + } + // the button has an associated label + if (hasAssociatedLabelViaAriaLabelledBy(openingElement, context)) { + return; + } + const hasAccessibleLabelling = hasNonEmptyProp(openingElement.attributes, "title") || hasNonEmptyProp(openingElement.attributes, "aria-label"); + // if it has no accessible name, report error + if (!hasAccessibleLabelling) { + context.report({ + node, + messageId: `missingAriaLabel` + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/buttons/no-empty-buttons.d.ts b/dist/lib/rules/buttons/no-empty-buttons.d.ts new file mode 100644 index 0000000..50dc5dd --- /dev/null +++ b/dist/lib/rules/buttons/no-empty-buttons.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noEmptyButtons: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXElement(node: any): any; +}; +//# sourceMappingURL=no-empty-buttons.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/buttons/no-empty-buttons.d.ts.map b/dist/lib/rules/buttons/no-empty-buttons.d.ts.map new file mode 100644 index 0000000..f0d6a17 --- /dev/null +++ b/dist/lib/rules/buttons/no-empty-buttons.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-empty-buttons.d.ts","sourceRoot":"","sources":["../../../../lib/rules/buttons/no-empty-buttons.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EAmCC"} \ No newline at end of file diff --git a/dist/lib/rules/buttons/no-empty-buttons.js b/dist/lib/rules/buttons/no-empty-buttons.js new file mode 100644 index 0000000..78b0856 --- /dev/null +++ b/dist/lib/rules/buttons/no-empty-buttons.js @@ -0,0 +1,60 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +const allowedComponents = ["Button", "ToggleButton", "SplitButton", "MenuButton", "CompoundButton"]; +module.exports = { + meta: { + // possible error messages for the lint rule + messages: { + noEmptyButtons: `Accessibility: no empty ${allowedComponents.join(", ")}` + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: `Accessibility: ${allowedComponents.join(", ")} must either text content or icon or child component`, + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] // no 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) { + const openingElement = node.openingElement; + // if it is not a button, return + if (!allowedComponents.includes(elementType(openingElement))) { + return; + } + // if it has text content, return + if (hasTextContentChild(node)) + return; + // if there is icon prop, return + if (hasProp(openingElement.attributes, "icon")) { + return; + } + // if split button has secondary content, return + if (elementType(openingElement) === "CompoundButton" && hasNonEmptyProp(openingElement.attribute, "secondaryContent")) { + return; + } + const hasChildren = node.children.length > 0; + // if there are children, return + if (hasChildren) + return; + return context.report({ + node, + messageId: `noEmptyButtons` + }); + } + }; + } +}; diff --git a/dist/lib/rules/checkbox-needs-labelling.d.ts b/dist/lib/rules/checkbox-needs-labelling.d.ts new file mode 100644 index 0000000..fbc53e8 --- /dev/null +++ b/dist/lib/rules/checkbox-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabelledCheckbox: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=checkbox-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/checkbox-needs-labelling.d.ts.map b/dist/lib/rules/checkbox-needs-labelling.d.ts.map new file mode 100644 index 0000000..3cb4077 --- /dev/null +++ b/dist/lib/rules/checkbox-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"checkbox-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/checkbox-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA2BC"} \ No newline at end of file diff --git a/dist/lib/rules/checkbox-needs-labelling.js b/dist/lib/rules/checkbox-needs-labelling.js new file mode 100644 index 0000000..24e84d4 --- /dev/null +++ b/dist/lib/rules/checkbox-needs-labelling.js @@ -0,0 +1,53 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledCheckbox: "Accessibility: Checkbox without label must have an accessible and visual label: aria-labelledby" + }, + // "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: { + // DONE + description: "Accessibility: Checkbox without label must have an accessible and visual label: aria-labelledby", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a Checkbox, return + if (elementType(node) !== "Checkbox") { + return; + } + // if the Checkbox has a label, if the Switch has an associated label, return + if (hasNonEmptyProp(node.attributes, "label") || + hasFieldParent(context) || + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledCheckbox` + }); + } + }; + } +}; diff --git a/dist/lib/rules/combobox-needs-labelling.d.ts b/dist/lib/rules/combobox-needs-labelling.d.ts new file mode 100644 index 0000000..f4263ea --- /dev/null +++ b/dist/lib/rules/combobox-needs-labelling.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=combobox-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/combobox-needs-labelling.d.ts.map b/dist/lib/rules/combobox-needs-labelling.d.ts.map new file mode 100644 index 0000000..a4ff66c --- /dev/null +++ b/dist/lib/rules/combobox-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"combobox-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/combobox-needs-labelling.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/combobox-needs-labelling.js b/dist/lib/rules/combobox-needs-labelling.js new file mode 100644 index 0000000..ebd4427 --- /dev/null +++ b/dist/lib/rules/combobox-needs-labelling.js @@ -0,0 +1,53 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledCombobox: "Accessibility: Combobox must have an accessible label" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + docs: { + description: "All interactive elements must have an accessible name", + 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) { + // if it is not a Combobox, return + if (elementType(node) !== "Combobox") { + return; + } + // if the Combobox has a label, if the Combobox has an associated label, return + if (hasFieldParent(context) || + hasNonEmptyProp(node.attributes, "aria-label") || //aria-label, not recommended but will work for screen reader users + isInsideLabelTag(context) || // wrapped in label + hasAssociatedLabelViaHtmlFor(node, context) || // label with htmlFor + hasAssociatedLabelViaAriaLabelledBy(node, context) // aria-labelledby + ) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledCombobox` + }); + } + }; + } +}; diff --git a/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts new file mode 100644 index 0000000..68601dd --- /dev/null +++ b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=dialogbody-needs-title-content-and-actions.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map new file mode 100644 index 0000000..78b29e7 --- /dev/null +++ b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"dialogbody-needs-title-content-and-actions.d.ts","sourceRoot":"","sources":["../../../lib/rules/dialogbody-needs-title-content-and-actions.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/dialogbody-needs-title-content-and-actions.js b/dist/lib/rules/dialogbody-needs-title-content-and-actions.js new file mode 100644 index 0000000..ef3deb6 --- /dev/null +++ b/dist/lib/rules/dialogbody-needs-title-content-and-actions.js @@ -0,0 +1,41 @@ +// 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/dist/lib/rules/dialogsurface-needs-aria.d.ts b/dist/lib/rules/dialogsurface-needs-aria.d.ts new file mode 100644 index 0000000..82946b7 --- /dev/null +++ b/dist/lib/rules/dialogsurface-needs-aria.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=dialogsurface-needs-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/dialogsurface-needs-aria.d.ts.map b/dist/lib/rules/dialogsurface-needs-aria.d.ts.map new file mode 100644 index 0000000..509d523 --- /dev/null +++ b/dist/lib/rules/dialogsurface-needs-aria.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"dialogsurface-needs-aria.d.ts","sourceRoot":"","sources":["../../../lib/rules/dialogsurface-needs-aria.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/dialogsurface-needs-aria.js b/dist/lib/rules/dialogsurface-needs-aria.js new file mode 100644 index 0000000..3889df6 --- /dev/null +++ b/dist/lib/rules/dialogsurface-needs-aria.js @@ -0,0 +1,59 @@ +// 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/dist/lib/rules/dropdown-needs-labelling.d.ts b/dist/lib/rules/dropdown-needs-labelling.d.ts new file mode 100644 index 0000000..90012da --- /dev/null +++ b/dist/lib/rules/dropdown-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let missingLabelOrAriaLabeledByInDropdown: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: null; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=dropdown-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/dropdown-needs-labelling.d.ts.map b/dist/lib/rules/dropdown-needs-labelling.d.ts.map new file mode 100644 index 0000000..dc59601 --- /dev/null +++ b/dist/lib/rules/dropdown-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"dropdown-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/dropdown-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA+BI;;EA6BC"} \ No newline at end of file diff --git a/dist/lib/rules/dropdown-needs-labelling.js b/dist/lib/rules/dropdown-needs-labelling.js new file mode 100644 index 0000000..d4b19f8 --- /dev/null +++ b/dist/lib/rules/dropdown-needs-labelling.js @@ -0,0 +1,53 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + missingLabelOrAriaLabeledByInDropdown: "Accessibility: Dropdown mising label or missing aria-labelledby" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Dropdown menu must have an id and it needs to be linked via htmlFor of a Label", + recommended: true, + url: null + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a Dropdown, return + if (elementType(node) !== "Dropdown") { + return; + } + // if the dropdown has a aria-LabeledBy with same value present in id of Label, return (Most recommended) + // if the dropdown has an id and a label with htmlFor with sanme value as id, return + // if the dropdown has an associated label, return + // if the dropdown is inside Label tag, return + if (hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context) || + hasNonEmptyProp(node.attributes, "aria-label") || + isInsideLabelTag(context)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `missingLabelOrAriaLabeledByInDropdown` + }); + } + }; + } +}; diff --git a/dist/lib/rules/image-link-missing-aria.d.ts b/dist/lib/rules/image-link-missing-aria.d.ts new file mode 100644 index 0000000..ce06f0b --- /dev/null +++ b/dist/lib/rules/image-link-missing-aria.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=image-link-missing-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/image-link-missing-aria.d.ts.map b/dist/lib/rules/image-link-missing-aria.d.ts.map new file mode 100644 index 0000000..c2faf9c --- /dev/null +++ b/dist/lib/rules/image-link-missing-aria.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"image-link-missing-aria.d.ts","sourceRoot":"","sources":["../../../lib/rules/image-link-missing-aria.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/image-link-missing-aria.js b/dist/lib/rules/image-link-missing-aria.js new file mode 100644 index 0000000..1d34ebb --- /dev/null +++ b/dist/lib/rules/image-link-missing-aria.js @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +const { hasProp, elementType } = require("jsx-ast-utils"); +const { flattenChildren } = require("../util/flattenChildren"); +const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: "problem", + docs: { + description: "Accessibility: Image links must have an accessible name", + recommended: true, + url: "https://www.w3.org/WAI/standards-guidelines/act/rules/c487ae/" // URL to the documentation page for this rule + }, + messages: { + missingAriaLabel: "Accessibility Rule: Image links must have an accessible name. Link can have a title attribute or text content, or Image can have an aria-label, aria-labelledby, or title attribute." + }, + fixable: "code", + 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) { + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + if (elementType(node) === "Link") { + const flatChildren = flattenChildren(node.parent); + // Check if there is text content + const hasTextContent = flatChildren.some(child => (child.type === "JSXText" ? child.value.trim().length > 0 : false)); + if (hasTextContent) + return; + // Check if there is an accessible link + const hasAccessibleLink = hasNonEmptyProp(node.attributes, "title") || + hasNonEmptyProp(node.attributes, "aria-label") || + hasNonEmptyProp(node.attributes, "aria-labelledby"); + if (hasAccessibleLink) + return; + // Check if there is an accessible image + const hasAccessibleImage = flatChildren.some(child => { + if (child.type === "JSXElement" && child.openingElement.name.name === "Image") { + return hasProp(child.openingElement.attributes, "aria-hidden") + ? false + : hasNonEmptyProp(child.openingElement.attributes, "title") || + hasNonEmptyProp(child.openingElement.attributes, "aria-label") || + hasNonEmptyProp(child.openingElement.attributes, "aria-labelledby"); + } + return false; + }); + if (hasAccessibleImage) + return; + // Report if there is no text content, accessible link or image + if (!hasTextContent && !hasAccessibleLink && !hasAccessibleImage) { + context.report({ + node, + messageId: `missingAriaLabel` + }); + } + } + } + }; + } +}; diff --git a/dist/lib/rules/input-components-require-accessible-name.d.ts b/dist/lib/rules/input-components-require-accessible-name.d.ts new file mode 100644 index 0000000..1e8e19c --- /dev/null +++ b/dist/lib/rules/input-components-require-accessible-name.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let missingLabelOnInput: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=input-components-require-accessible-name.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/input-components-require-accessible-name.d.ts.map b/dist/lib/rules/input-components-require-accessible-name.d.ts.map new file mode 100644 index 0000000..c78ee40 --- /dev/null +++ b/dist/lib/rules/input-components-require-accessible-name.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"input-components-require-accessible-name.d.ts","sourceRoot":"","sources":["../../../lib/rules/input-components-require-accessible-name.js"],"names":[],"mappings":";;;;;;;;;;;;AAkCI;;EAyBC"} \ No newline at end of file diff --git a/dist/lib/rules/input-components-require-accessible-name.js b/dist/lib/rules/input-components-require-accessible-name.js new file mode 100644 index 0000000..a63ee2f --- /dev/null +++ b/dist/lib/rules/input-components-require-accessible-name.js @@ -0,0 +1,49 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + missingLabelOnInput: `Accessibility - input fields must have a aria label associated with it: ${applicableComponents.join(", ")}` + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Input fields must have accessible labelling: aria-label, aria-labelledby or an associated label", + recommended: true, + url: "https://www.w3.org/WAI/tutorials/forms/labels/" // 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 listed component, return + if (!applicableComponents.includes(elementType(node))) { + return; + } + // wrapped in Label tag, labelled with htmlFor, labelled with aria-labelledby + if (hasFieldParent(context) || + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + context.report({ + node, + messageId: `missingLabelOnInput` + }); + } + }; + } +}; diff --git a/dist/lib/rules/menu-item-needs-labelling.d.ts b/dist/lib/rules/menu-item-needs-labelling.d.ts new file mode 100644 index 0000000..50ba124 --- /dev/null +++ b/dist/lib/rules/menu-item-needs-labelling.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=menu-item-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/menu-item-needs-labelling.d.ts.map b/dist/lib/rules/menu-item-needs-labelling.d.ts.map new file mode 100644 index 0000000..e03d3cf --- /dev/null +++ b/dist/lib/rules/menu-item-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"menu-item-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/menu-item-needs-labelling.js"],"names":[],"mappings":"wBAeW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/menu-item-needs-labelling.js b/dist/lib/rules/menu-item-needs-labelling.js new file mode 100644 index 0000000..3e2e296 --- /dev/null +++ b/dist/lib/rules/menu-item-needs-labelling.js @@ -0,0 +1,54 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledMenuItem: "Accessibility: MenuItem must have an accessible label" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + docs: { + description: "Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby", + recommended: true, + 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) { + const openingElement = node.openingElement; + // if it is not a MenuItem, return + if (elementType(openingElement) !== "MenuItem") { + return; + } + // if the MenuItem has a text, label or an associated label, return + if (hasNonEmptyProp(openingElement.attributes, "aria-label") || //aria-label, not recommended but will work for screen reader users + hasAssociatedLabelViaAriaLabelledBy(openingElement, context) || // aria-labelledby + hasTextContentChild(node) || // has text content + hasToolTipParent(context) // has tooltip parent, not recommended but will work for screen reader users + ) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledMenuItem` + }); + } + }; + } +}; diff --git a/dist/lib/rules/no-empty-components.d.ts b/dist/lib/rules/no-empty-components.d.ts new file mode 100644 index 0000000..d7884b0 --- /dev/null +++ b/dist/lib/rules/no-empty-components.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-empty-components.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/no-empty-components.d.ts.map b/dist/lib/rules/no-empty-components.d.ts.map new file mode 100644 index 0000000..29f5bda --- /dev/null +++ b/dist/lib/rules/no-empty-components.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-empty-components.d.ts","sourceRoot":"","sources":["../../../lib/rules/no-empty-components.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/no-empty-components.js b/dist/lib/rules/no-empty-components.js new file mode 100644 index 0000000..daae293 --- /dev/null +++ b/dist/lib/rules/no-empty-components.js @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +var elementType = require("jsx-ast-utils").elementType; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +// Define an array of allowed component names +const allowedComponents = ["Text", "Label", "Combobox", "Breadcrumb", "Dropdown", "Accordion", "AccordionItem", "AccordionPanel"]; +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the lint rule + messages: { + noEmptyComponents: `Accessibility: no empty ${allowedComponents.join(", ")} components` + }, + 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 + }, + 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) { + const openingElement = node.openingElement; + // if it is not a listed component, return + if (!allowedComponents.includes(elementType(openingElement))) { + return; + } + const hasChildren = node.children.length > 0; + // if there are no children, report error + if (!hasChildren) { + context.report({ + node, + messageId: `noEmptyComponents` + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/prefer-aria-over-title-attribute.d.ts b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts new file mode 100644 index 0000000..23a72d6 --- /dev/null +++ b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=prefer-aria-over-title-attribute.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map new file mode 100644 index 0000000..7582fa6 --- /dev/null +++ b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"prefer-aria-over-title-attribute.d.ts","sourceRoot":"","sources":["../../../lib/rules/prefer-aria-over-title-attribute.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/prefer-aria-over-title-attribute.js b/dist/lib/rules/prefer-aria-over-title-attribute.js new file mode 100644 index 0000000..9841805 --- /dev/null +++ b/dist/lib/rules/prefer-aria-over-title-attribute.js @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +const { elementType } = require("jsx-ast-utils"); +const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); +var hasProp = require("jsx-ast-utils").hasProp; +const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); +const { hasToolTipParent } = require("../util/hasTooltipParent"); +const { hasTextContentChild } = require("../util/hasTextContentChild"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +const applicableComponents = ["Button"]; +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + preferAria: `Prefer aria over the title attribute for accessible labelling: ${applicableComponents.join(", ")}` + }, + type: "suggestion", // `problem`, `suggestion`, or `layout` + docs: { + description: "The title attribute is not consistently read by screen readers, and its behavior can vary depending on the screen reader and the user's settings.", + recommended: true, + url: null // URL to the documentation page for this rule + }, + fixable: "code", // 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) { + const openingElement = node.openingElement; + // if it is not a listed component, return + if (!applicableComponents.includes(elementType(openingElement))) { + return; + } + // if it is not an icon button, return + if (!hasProp(openingElement.attributes, "icon")) { + return; + } + // if it has a tooltip parent, return + if (hasToolTipParent(context)) { + return; + } + // if it has text content, return + if (hasTextContentChild(node)) { + return; + } + // the button has an associated label + if (hasAssociatedLabelViaAriaLabelledBy(openingElement, context)) { + return; + } + const hasAria = hasNonEmptyProp(openingElement.attributes, "aria-label"); + const hasTitle = hasNonEmptyProp(openingElement.attributes, "title"); + // if it has no accessible name, report error + if (hasTitle && !hasAria) { + context.report({ + node, + messageId: `preferAria`, + fix(fixer) { + const attributes = openingElement.attributes; + const titleAttribute = attributes.find(attr => attr.name && attr.name.name === "title"); + // Generate the aria-label attribute + const ariaLabel = ` aria-label="${titleAttribute.value.value}"`; + // Find the location to insert the new attribute + const lastAttribute = attributes[attributes.length - 1]; + const insertPosition = lastAttribute.range[1]; + return fixer.insertTextAfterRange([insertPosition, insertPosition], ariaLabel); + } + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/progressbar-needs-labelling.d.ts b/dist/lib/rules/progressbar-needs-labelling.d.ts new file mode 100644 index 0000000..44391aa --- /dev/null +++ b/dist/lib/rules/progressbar-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabelledProgressbar: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=progressbar-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/progressbar-needs-labelling.d.ts.map b/dist/lib/rules/progressbar-needs-labelling.d.ts.map new file mode 100644 index 0000000..848cf0d --- /dev/null +++ b/dist/lib/rules/progressbar-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"progressbar-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/progressbar-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA0CC"} \ No newline at end of file diff --git a/dist/lib/rules/progressbar-needs-labelling.js b/dist/lib/rules/progressbar-needs-labelling.js new file mode 100644 index 0000000..33edfbf --- /dev/null +++ b/dist/lib/rules/progressbar-needs-labelling.js @@ -0,0 +1,61 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledProgressbar: "Accessibility: Progressbar must have aria-valuemin, aria-valuemax, aria-valuenow, aria-describedby and either aria-label or aria-labelledby attributes" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Progressbar must have aria-valuemin, aria-valuemax, aria-valuenow, aria-describedby and either aria-label or aria-labelledby attributes", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a ProgressBar, return + if (elementType(node) !== "ProgressBar") { + return; + } + // check if the ProgressBar has a Field parent + const hasFieldParentCheck = hasFieldParent(context); + // If no Field parent, ensure one of the aria-label or aria-labelledby is provided as well as aria-describedby + const hasLabelling = (hasNonEmptyProp(node.attributes, "aria-label") || hasNonEmptyProp(node.attributes, "aria-labelledby")) && + hasNonEmptyProp(node.attributes, "aria-describedby"); + const mandatoryAttributes = []; + // Check if max is provided, if not, require aria-valuemax + const hasMaxProp = hasNonEmptyProp(node.attributes, "max"); + if (!hasMaxProp) { + mandatoryAttributes.push("aria-valuemax"); + mandatoryAttributes.push("aria-valuemin"); + mandatoryAttributes.push("aria-valuenow"); + } + // If all mandatory attributes (including optional aria-valuemax) are present, return + if (mandatoryAttributes.every(attribute => hasNonEmptyProp(node.attributes, attribute)) && + (hasFieldParentCheck || hasLabelling)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledProgressbar` + }); + } + }; + } +}; diff --git a/dist/lib/rules/radio-button-missing-label.d.ts b/dist/lib/rules/radio-button-missing-label.d.ts new file mode 100644 index 0000000..5fc986b --- /dev/null +++ b/dist/lib/rules/radio-button-missing-label.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabeledRadioButton: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=radio-button-missing-label.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/radio-button-missing-label.d.ts.map b/dist/lib/rules/radio-button-missing-label.d.ts.map new file mode 100644 index 0000000..0f73246 --- /dev/null +++ b/dist/lib/rules/radio-button-missing-label.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"radio-button-missing-label.d.ts","sourceRoot":"","sources":["../../../lib/rules/radio-button-missing-label.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA4BC"} \ No newline at end of file diff --git a/dist/lib/rules/radio-button-missing-label.js b/dist/lib/rules/radio-button-missing-label.js new file mode 100644 index 0000000..0335354 --- /dev/null +++ b/dist/lib/rules/radio-button-missing-label.js @@ -0,0 +1,54 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabeledRadioButton: "Accessibility: Radio button without label must have an accessible and visual label: aria-labelledby" + }, + // "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: { + // DONE + description: "Accessibility: Radio button without label must have an accessible and visual label: aria-labelledby", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a Checkbox, return + if (elementType(node) !== "Radio") { + return; + } + // if the Checkbox has a label, if the Switch has an associated label, return + if (hasFieldParent(context) || + hasNonEmptyProp(node.attributes, "label") || + hasNonEmptyProp(node.attributes, "aria-label") || + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabeledRadioButton` + }); + } + }; + } +}; diff --git a/dist/lib/rules/radiogroup-missing-label.d.ts b/dist/lib/rules/radiogroup-missing-label.d.ts new file mode 100644 index 0000000..67d6666 --- /dev/null +++ b/dist/lib/rules/radiogroup-missing-label.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabeledRadioGroup: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=radiogroup-missing-label.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/radiogroup-missing-label.d.ts.map b/dist/lib/rules/radiogroup-missing-label.d.ts.map new file mode 100644 index 0000000..5a20574 --- /dev/null +++ b/dist/lib/rules/radiogroup-missing-label.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"radiogroup-missing-label.d.ts","sourceRoot":"","sources":["../../../lib/rules/radiogroup-missing-label.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA4BC"} \ No newline at end of file diff --git a/dist/lib/rules/radiogroup-missing-label.js b/dist/lib/rules/radiogroup-missing-label.js new file mode 100644 index 0000000..98b15b6 --- /dev/null +++ b/dist/lib/rules/radiogroup-missing-label.js @@ -0,0 +1,54 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabeledRadioGroup: "Accessibility: RadioGroup without label must have an accessible and visual label: aria-labelledby" + }, + // "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: { + // DONE + description: "Accessibility: RadioGroup without label must have an accessible and visual label: aria-labelledby", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a Checkbox, return + if (elementType(node) !== "RadioGroup") { + return; + } + // if the Checkbox has a label, if the Switch has an associated label, return + if (hasFieldParent(context) || + hasNonEmptyProp(node.attributes, "label") || + hasNonEmptyProp(node.attributes, "aria-label") || + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabeledRadioGroup` + }); + } + }; + } +}; diff --git a/dist/lib/rules/spin-button-needs-labelling.d.ts b/dist/lib/rules/spin-button-needs-labelling.d.ts new file mode 100644 index 0000000..628b05d --- /dev/null +++ b/dist/lib/rules/spin-button-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabelledSpinButton: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=spin-button-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/spin-button-needs-labelling.d.ts.map b/dist/lib/rules/spin-button-needs-labelling.d.ts.map new file mode 100644 index 0000000..5437d0b --- /dev/null +++ b/dist/lib/rules/spin-button-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spin-button-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/spin-button-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA8BI;;EA0BC"} \ No newline at end of file diff --git a/dist/lib/rules/spin-button-needs-labelling.js b/dist/lib/rules/spin-button-needs-labelling.js new file mode 100644 index 0000000..27ccbbe --- /dev/null +++ b/dist/lib/rules/spin-button-needs-labelling.js @@ -0,0 +1,50 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledSpinButton: "Accessibility: SpinButtons must have an accessible label" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: SpinButtons must have an accessible label", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a SpinButton, return + if (elementType(node) !== "SpinButton") { + return; + } + // if the SpinButton has an associated label, return + if (hasFieldParent(context) || + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledSpinButton` + }); + } + }; + } +}; diff --git a/dist/lib/rules/spin-button-unrecommended-labelling.d.ts b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts new file mode 100644 index 0000000..a66d49c --- /dev/null +++ b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let unRecommendedlabellingSpinButton: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=spin-button-unrecommended-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map new file mode 100644 index 0000000..cfca0ab --- /dev/null +++ b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spin-button-unrecommended-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/spin-button-unrecommended-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA8BI;;EAkBC"} \ No newline at end of file diff --git a/dist/lib/rules/spin-button-unrecommended-labelling.js b/dist/lib/rules/spin-button-unrecommended-labelling.js new file mode 100644 index 0000000..9dae79e --- /dev/null +++ b/dist/lib/rules/spin-button-unrecommended-labelling.js @@ -0,0 +1,45 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible suggestion messages for the rule + messages: { + unRecommendedlabellingSpinButton: "Accessibility: Unrecommended accessibility labelling - SpinButton" + }, + // "problem" means the rule is identifying something that could be done in a better way but no errors will occur if the code isn’t changed: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "suggestion", + // docs for the rule + docs: { + description: "Accessibility: Unrecommended accessibility labelling - SpinButton", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a SpinButton, return + if (elementType(node) !== "SpinButton") { + return; + } + // if the SpinButton has an aria-label or is wrapped in a Tooltip, show warning + if (hasNonEmptyProp(node.attributes, "aria-label") || hasToolTipParent(context)) { + context.report({ + node, + messageId: `unRecommendedlabellingSpinButton` + }); + } + } + }; + } +}; diff --git a/dist/lib/rules/spinner-needs-labelling.d.ts b/dist/lib/rules/spinner-needs-labelling.d.ts new file mode 100644 index 0000000..069b916 --- /dev/null +++ b/dist/lib/rules/spinner-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabelledSpinner: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=spinner-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/spinner-needs-labelling.d.ts.map b/dist/lib/rules/spinner-needs-labelling.d.ts.map new file mode 100644 index 0000000..0a8d190 --- /dev/null +++ b/dist/lib/rules/spinner-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spinner-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/spinner-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA6BI;;EAuBC"} \ No newline at end of file diff --git a/dist/lib/rules/spinner-needs-labelling.js b/dist/lib/rules/spinner-needs-labelling.js new file mode 100644 index 0000000..c4f8aa5 --- /dev/null +++ b/dist/lib/rules/spinner-needs-labelling.js @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); +const elementType = require("jsx-ast-utils").elementType; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledSpinner: "Accessibility: Spinner must have either aria-label or label, aria-live and aria-busy attributes" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Spinner must have either aria-label or label, aria-live and aria-busy attributes", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a Spinner, return + if (elementType(node) !== "Spinner") { + return; + } + if (hasNonEmptyProp(node.attributes, "aria-busy") + && hasNonEmptyProp(node.attributes, "aria-live") + && (hasNonEmptyProp(node.attributes, "label") || hasNonEmptyProp(node.attributes, "aria-label"))) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledSpinner` + }); + } + }; + } +}; diff --git a/dist/lib/rules/switch-needs-labelling.d.ts b/dist/lib/rules/switch-needs-labelling.d.ts new file mode 100644 index 0000000..de45a22 --- /dev/null +++ b/dist/lib/rules/switch-needs-labelling.d.ts @@ -0,0 +1,16 @@ +export namespace meta { + namespace messages { + let noUnlabelledSwitch: string; + } + let type: string; + namespace docs { + let description: string; + let recommended: boolean; + let url: string; + } + let schema: never[]; +} +export function create(context: any): { + JSXOpeningElement(node: any): void; +}; +//# sourceMappingURL=switch-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/switch-needs-labelling.d.ts.map b/dist/lib/rules/switch-needs-labelling.d.ts.map new file mode 100644 index 0000000..7dccf88 --- /dev/null +++ b/dist/lib/rules/switch-needs-labelling.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"switch-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/switch-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA+BI;;EA2BC"} \ No newline at end of file diff --git a/dist/lib/rules/switch-needs-labelling.js b/dist/lib/rules/switch-needs-labelling.js new file mode 100644 index 0000000..6013633 --- /dev/null +++ b/dist/lib/rules/switch-needs-labelling.js @@ -0,0 +1,52 @@ +// 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"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledSwitch: "Accessibility: Switch must have an accessible label" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Switch must have an accessible label", + recommended: true, + url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule + }, + schema: [] + }, + // create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree + create(context) { + return { + // visitor functions for different types of nodes + JSXOpeningElement(node) { + // if it is not a Switch, return + if (elementType(node) !== "Switch") { + return; + } + // if the Switch has a label, if the Switch has an associated label, return + if (hasNonEmptyProp(node.attributes, "label") || + hasFieldParent(context) || + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledSwitch` + }); + } + }; + } +}; diff --git a/dist/lib/rules/toolbar-missing-aria.d.ts b/dist/lib/rules/toolbar-missing-aria.d.ts new file mode 100644 index 0000000..c026736 --- /dev/null +++ b/dist/lib/rules/toolbar-missing-aria.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=toolbar-missing-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/toolbar-missing-aria.d.ts.map b/dist/lib/rules/toolbar-missing-aria.d.ts.map new file mode 100644 index 0000000..15a5f74 --- /dev/null +++ b/dist/lib/rules/toolbar-missing-aria.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"toolbar-missing-aria.d.ts","sourceRoot":"","sources":["../../../lib/rules/toolbar-missing-aria.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/toolbar-missing-aria.js b/dist/lib/rules/toolbar-missing-aria.js new file mode 100644 index 0000000..926fb8f --- /dev/null +++ b/dist/lib/rules/toolbar-missing-aria.js @@ -0,0 +1,46 @@ +// 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; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the rule + messages: { + missingLabelOnToolbar: "Toolbars need accessible labelling: aria-label or aria-labelledby" + }, + // "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "problem", + // docs for the rule + docs: { + description: "Accessibility: Toolbars need accessible labelling: aria-label or aria-labelledby", + recommended: true, + url: "https://www.w3.org/WAI/tutorials/forms/labels/" // 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 Toolbar, return + if (elementType(node) !== "Toolbar") { + return; + } + // if the Toolbar has aria labelling, return + if (hasNonEmptyProp(node.attributes, "aria-label") || hasAssociatedLabelViaAriaLabelledBy(node, context)) { + return; + } + context.report({ + node, + messageId: `missingLabelOnToolbar` + }); + } + }; + } +}; diff --git a/dist/lib/rules/tooltip-not-recommended.d.ts b/dist/lib/rules/tooltip-not-recommended.d.ts new file mode 100644 index 0000000..39af2fb --- /dev/null +++ b/dist/lib/rules/tooltip-not-recommended.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import("eslint").Rule.RuleModule; +export = _exports; +//# sourceMappingURL=tooltip-not-recommended.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/tooltip-not-recommended.d.ts.map b/dist/lib/rules/tooltip-not-recommended.d.ts.map new file mode 100644 index 0000000..9e729d8 --- /dev/null +++ b/dist/lib/rules/tooltip-not-recommended.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tooltip-not-recommended.d.ts","sourceRoot":"","sources":["../../../lib/rules/tooltip-not-recommended.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/tooltip-not-recommended.js b/dist/lib/rules/tooltip-not-recommended.js new file mode 100644 index 0000000..55f893e --- /dev/null +++ b/dist/lib/rules/tooltip-not-recommended.js @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +"use strict"; +var elementType = require("jsx-ast-utils").elementType; +const { hasToolTipParent } = require("../util/hasTooltipParent"); +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +// Define an array of allowed component names +const allowedComponents = ["MenuItem", "SpinButton"]; +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + // possible error messages for the lint rule + messages: { + tooltipNotRecommended: `Accessibility: Tooltop not recommended for these components ${allowedComponents.join(", ")}` + }, + 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 + }, + 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) { + const openingElement = node.openingElement; + // if it is not a listed component, return + if (!allowedComponents.includes(elementType(openingElement))) { + return; + } + // if there are is tooltip, report + if (hasToolTipParent(context)) { + context.report({ + node, + messageId: `tooltipNotRecommended` + }); + } + } + }; + } +}; diff --git a/dist/lib/util/flattenChildren.d.ts b/dist/lib/util/flattenChildren.d.ts new file mode 100644 index 0000000..44d402a --- /dev/null +++ b/dist/lib/util/flattenChildren.d.ts @@ -0,0 +1,2 @@ +export function flattenChildren(node: any): any[]; +//# sourceMappingURL=flattenChildren.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/flattenChildren.d.ts.map b/dist/lib/util/flattenChildren.d.ts.map new file mode 100644 index 0000000..8952eee --- /dev/null +++ b/dist/lib/util/flattenChildren.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"flattenChildren.d.ts","sourceRoot":"","sources":["../../../lib/util/flattenChildren.js"],"names":[],"mappings":"AAIA,kDAcC"} \ No newline at end of file diff --git a/dist/lib/util/flattenChildren.js b/dist/lib/util/flattenChildren.js new file mode 100644 index 0000000..f9b7b8a --- /dev/null +++ b/dist/lib/util/flattenChildren.js @@ -0,0 +1,19 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// TODO: add comments +function flattenChildren(node) { + const flatChildren = []; + if (node.children && node.children.length > 0) { + node.children.forEach(child => { + if (child.type === 'JSXElement' && child.children && child.children.length > 0) { + flatChildren.push(child, ...flattenChildren(child)); + } + else { + flatChildren.push(child); + } + }); + } + return flatChildren; +} +module.exports.flattenChildren = flattenChildren; diff --git a/dist/lib/util/hasFieldParent.d.ts b/dist/lib/util/hasFieldParent.d.ts new file mode 100644 index 0000000..5920a89 --- /dev/null +++ b/dist/lib/util/hasFieldParent.d.ts @@ -0,0 +1,2 @@ +export function hasFieldParent(context: any): boolean; +//# sourceMappingURL=hasFieldParent.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasFieldParent.d.ts.map b/dist/lib/util/hasFieldParent.d.ts.map new file mode 100644 index 0000000..f1a56e4 --- /dev/null +++ b/dist/lib/util/hasFieldParent.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"hasFieldParent.d.ts","sourceRoot":"","sources":["../../../lib/util/hasFieldParent.js"],"names":[],"mappings":"AAKA,sDAqBC"} \ No newline at end of file diff --git a/dist/lib/util/hasFieldParent.js b/dist/lib/util/hasFieldParent.js new file mode 100644 index 0000000..a142ecb --- /dev/null +++ b/dist/lib/util/hasFieldParent.js @@ -0,0 +1,23 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +var elementType = require("jsx-ast-utils").elementType; +function hasFieldParent(context) { + const ancestors = context.getAncestors(); + if (ancestors == null || ancestors.length === 0) { + return false; + } + var field = false; + ancestors.forEach(item => { + if (item.type === "JSXElement" && + item.openingElement != null && + item.openingElement.type === "JSXOpeningElement" && + elementType(item.openingElement) === "Field") { + field = true; + } + }); + return field; +} +module.exports = { + hasFieldParent +}; diff --git a/dist/lib/util/hasNonEmptyProp.d.ts b/dist/lib/util/hasNonEmptyProp.d.ts new file mode 100644 index 0000000..7409085 --- /dev/null +++ b/dist/lib/util/hasNonEmptyProp.d.ts @@ -0,0 +1,8 @@ +/** + * Determines if the prop exists and has a non-empty value. + * @param {*} attributes + * @param {*} name + * @returns boolean + */ +export function hasNonEmptyProp(attributes: any, name: any): boolean; +//# sourceMappingURL=hasNonEmptyProp.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasNonEmptyProp.d.ts.map b/dist/lib/util/hasNonEmptyProp.d.ts.map new file mode 100644 index 0000000..e12ab45 --- /dev/null +++ b/dist/lib/util/hasNonEmptyProp.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"hasNonEmptyProp.d.ts","sourceRoot":"","sources":["../../../lib/util/hasNonEmptyProp.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,4CAJW,GAAC,QACD,GAAC,WAmBX"} \ No newline at end of file diff --git a/dist/lib/util/hasNonEmptyProp.js b/dist/lib/util/hasNonEmptyProp.js new file mode 100644 index 0000000..1c4e749 --- /dev/null +++ b/dist/lib/util/hasNonEmptyProp.js @@ -0,0 +1,27 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +var hasProp = require("jsx-ast-utils").hasProp; +var getPropValue = require("jsx-ast-utils").getPropValue; +var getProp = require("jsx-ast-utils").getProp; +/** + * Determines if the prop exists and has a non-empty value. + * @param {*} attributes + * @param {*} name + * @returns boolean + */ +function hasNonEmptyProp(attributes, name) { + if (!hasProp(attributes, name)) { + return false; + } + const propValue = getPropValue(getProp(attributes, name)); + /** + * getPropValue internally normalizes "true", "false" to boolean values. + * So it is sufficent to check if the prop exists and return. + */ + if (typeof propValue === "boolean" || typeof propValue === "number") { + return true; + } + return propValue.trim().length > 0; +} +module.exports.hasNonEmptyProp = hasNonEmptyProp; diff --git a/dist/lib/util/hasTextContentChild.d.ts b/dist/lib/util/hasTextContentChild.d.ts new file mode 100644 index 0000000..bf5f42d --- /dev/null +++ b/dist/lib/util/hasTextContentChild.d.ts @@ -0,0 +1,7 @@ +/** + * hasTextContentChild - determines if a component has text content as a child e.g. + * @param {*} node JSXElement + * @returns boolean + */ +export function hasTextContentChild(node: any): boolean; +//# sourceMappingURL=hasTextContentChild.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasTextContentChild.d.ts.map b/dist/lib/util/hasTextContentChild.d.ts.map new file mode 100644 index 0000000..5b25d75 --- /dev/null +++ b/dist/lib/util/hasTextContentChild.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"hasTextContentChild.d.ts","sourceRoot":"","sources":["../../../lib/util/hasTextContentChild.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,0CAHW,GAAC,WAcX"} \ No newline at end of file diff --git a/dist/lib/util/hasTextContentChild.js b/dist/lib/util/hasTextContentChild.js new file mode 100644 index 0000000..646e498 --- /dev/null +++ b/dist/lib/util/hasTextContentChild.js @@ -0,0 +1,21 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/** + * hasTextContentChild - determines if a component has text content as a child e.g. + * @param {*} node JSXElement + * @returns boolean + */ +function hasTextContentChild(node) { + // no children + if (node.children == null || node.children == undefined || node.children.length === 0) { + return false; + } + const result = node.children.filter(element => { + return element.type === "JSXText" && element.value.trim().length > 0; + }); + return result != null; +} +module.exports = { + hasTextContentChild +}; diff --git a/dist/lib/util/hasTooltipParent.d.ts b/dist/lib/util/hasTooltipParent.d.ts new file mode 100644 index 0000000..1155136 --- /dev/null +++ b/dist/lib/util/hasTooltipParent.d.ts @@ -0,0 +1,2 @@ +export function hasToolTipParent(context: any): boolean; +//# sourceMappingURL=hasTooltipParent.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasTooltipParent.d.ts.map b/dist/lib/util/hasTooltipParent.d.ts.map new file mode 100644 index 0000000..a7f10d0 --- /dev/null +++ b/dist/lib/util/hasTooltipParent.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"hasTooltipParent.d.ts","sourceRoot":"","sources":["../../../lib/util/hasTooltipParent.js"],"names":[],"mappings":"AAKA,wDAqBC"} \ No newline at end of file diff --git a/dist/lib/util/hasTooltipParent.js b/dist/lib/util/hasTooltipParent.js new file mode 100644 index 0000000..7b21878 --- /dev/null +++ b/dist/lib/util/hasTooltipParent.js @@ -0,0 +1,23 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +var elementType = require("jsx-ast-utils").elementType; +function hasToolTipParent(context) { + const ancestors = context.getAncestors(); + if (ancestors == null || ancestors.length === 0) { + return false; + } + var toolTip = false; + ancestors.forEach(item => { + if (item.type === "JSXElement" && + item.openingElement != null && + item.openingElement.type === "JSXOpeningElement" && + elementType(item.openingElement) === "Tooltip") { + toolTip = true; + } + }); + return toolTip; +} +module.exports = { + hasToolTipParent +}; diff --git a/dist/lib/util/labelUtils.d.ts b/dist/lib/util/labelUtils.d.ts new file mode 100644 index 0000000..362acd5 --- /dev/null +++ b/dist/lib/util/labelUtils.d.ts @@ -0,0 +1,72 @@ +/** + * Checks if the element is nested within a Label tag. + * e.g. + * + * @param {*} context + * @returns + */ +export function isInsideLabelTag(context: any): any; +/** + * Checks if there is a Label component inside the source code with a htmlFor attribute matching that of the id parameter. + * e.g. + * id=parameter, + * @param {*} idValue + * @param {*} context + * @returns boolean for match found or not. + */ +export function hasLabelWithHtmlForId(idValue: any, context: any): boolean; +/** + * Checks if there is a Label component inside the source code with an id matching that of the id parameter. + * e.g. + * id=parameter, + * @param {*} idValue value of the props id e.g. + * + * + * @param {*} openingElement + * @param {*} context + * @param {*} ariaAttribute + * @returns boolean for match found or not. + */ +export function hasAssociatedAriaText(openingElement: any, context: any, ariaAttribute: any): boolean; +//# sourceMappingURL=labelUtils.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/labelUtils.d.ts.map b/dist/lib/util/labelUtils.d.ts.map new file mode 100644 index 0000000..801a5e4 --- /dev/null +++ b/dist/lib/util/labelUtils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"labelUtils.d.ts","sourceRoot":"","sources":["../../../lib/util/labelUtils.js"],"names":[],"mappings":"AAQA;;;;;;;;;GASG;AACH,0CAHW,GAAC,OAUX;AAED;;;;;;;GAOG;AACH,+CAJW,GAAC,WACD,GAAC,WAWX;AAED;;;;;;;GAOG;AACH,4CAJW,GAAC,WACD,GAAC,WAYX;AAED;;;;;;;;GAQG;AACH,oEAJW,GAAC,WACD,GAAC,WASX;AAmBD;;;;;;;;GAQG;AACH,6DAJW,GAAC,WACD,GAAC,WAOX;AA9BD;;;;;;;;GAQG;AACH,qEAJW,GAAC,WACD,GAAC,WASX;AAiBD;;;;;;;;;;GAUG;AACH,sDALW,GAAC,WACD,GAAC,iBACD,GAAC,WAqBX"} \ No newline at end of file diff --git a/dist/lib/util/labelUtils.js b/dist/lib/util/labelUtils.js new file mode 100644 index 0000000..736f988 --- /dev/null +++ b/dist/lib/util/labelUtils.js @@ -0,0 +1,136 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +var elementType = require("jsx-ast-utils").elementType; +var getPropValue = require("jsx-ast-utils").getPropValue; +var getProp = require("jsx-ast-utils").getProp; +const { hasNonEmptyProp } = require("./hasNonEmptyProp"); +/** + * Checks if the element is nested within a Label tag. + * e.g. + * + * @param {*} context + * @returns + */ +function isInsideLabelTag(context) { + return context + .getAncestors() + .some(node => node.type === "JSXElement" && (elementType(node.openingElement) === "Label" || elementType(node.openingElement) === "label")); +} +/** + * Checks if there is a Label component inside the source code with a htmlFor attribute matching that of the id parameter. + * e.g. + * id=parameter, + * @param {*} idValue + * @param {*} context + * @returns boolean for match found or not. + */ +function hasLabelWithHtmlForId(idValue, context) { + if (idValue === "") { + return false; + } + const sourceCode = context.getSourceCode(); + const regex = /]*htmlFor[^>]*=[^>]*[{"|{'|"|']([^>'"}]*)['|"|'}|"}][^>]*>/gim; + const matches = regex.exec(sourceCode.text); + return !!matches && matches.some(match => match === idValue); +} +/** + * Checks if there is a Label component inside the source code with an id matching that of the id parameter. + * e.g. + * id=parameter, + * @param {*} idValue value of the props id e.g. + * + * + * @param {*} openingElement + * @param {*} context + * @param {*} ariaAttribute + * @returns boolean for match found or not. + */ +function hasAssociatedAriaText(openingElement, context, ariaAttribute) { + const hasAssociatedAriaText = hasNonEmptyProp(openingElement.attributes, ariaAttribute); + const idValue = getPropValue(getProp(openingElement.attributes, ariaAttribute)); + let hasHtmlId = false; + if (idValue) { + const sourceCode = context.getSourceCode(); + const regex = /<(\w+)[^>]*id\s*=\s*["']([^"']*)["'][^>]*>/gi; + let match; + const ids = []; + while ((match = regex.exec(sourceCode.text)) !== null) { + ids.push(match[2]); + } + hasHtmlId = ids.some(id => id === idValue); + } + return hasAssociatedAriaText && hasHtmlId; +} +module.exports = { + isInsideLabelTag, + hasLabelWithHtmlForId, + hasLabelWithHtmlId, + hasAssociatedLabelViaAriaLabelledBy, + hasAssociatedLabelViaHtmlFor, + hasAssociatedLabelViaAriaDescribedby, + hasAssociatedAriaText +}; From fddfcc0fe9ea73cf343afed36ccfa8c7e98c1511 Mon Sep 17 00:00:00 2001 From: Ajay Agarwal Date: Thu, 19 Sep 2024 16:16:09 +0530 Subject: [PATCH 3/6] remove declaration files and added one rule for test --- .../buttonBasedComponents.d.ts | 1 - .../buttonBasedComponents.d.ts.map | 1 - .../inputBasedComponents.d.ts | 1 - .../inputBasedComponents.d.ts.map | 1 - dist/lib/index.d.ts | 289 +----------------- dist/lib/index.d.ts.map | 1 - dist/lib/index.js | 9 +- .../accordion-header-needs-labelling.d.ts | 1 - .../accordion-header-needs-labelling.d.ts.map | 1 - ...accordion-item-needs-header-and-panel.d.ts | 1 - ...rdion-item-needs-header-and-panel.d.ts.map | 1 - dist/lib/rules/avatar-needs-name.d.ts | 1 - dist/lib/rules/avatar-needs-name.d.ts.map | 1 - ...ria-describedby-for-primary-labelling.d.ts | 1 - ...describedby-for-primary-labelling.d.ts.map | 1 - .../rules/badge-needs-accessible-name.d.ts | 1 - .../badge-needs-accessible-name.d.ts.map | 1 - .../lib/rules/breadcrumb-needs-labelling.d.ts | 1 - .../rules/breadcrumb-needs-labelling.d.ts.map | 1 - .../compound-button-needs-labelling.d.ts | 1 - .../compound-button-needs-labelling.d.ts.map | 1 - .../buttons/image-button-missing-aria.d.ts | 1 - .../image-button-missing-aria.d.ts.map | 1 - dist/lib/rules/buttons/no-empty-buttons.d.ts | 1 - .../rules/buttons/no-empty-buttons.d.ts.map | 1 - dist/lib/rules/checkbox-needs-labelling.d.ts | 1 - .../rules/checkbox-needs-labelling.d.ts.map | 1 - dist/lib/rules/combobox-needs-labelling.d.ts | 1 - .../rules/combobox-needs-labelling.d.ts.map | 1 - ...gbody-needs-title-content-and-actions.d.ts | 1 - ...y-needs-title-content-and-actions.d.ts.map | 1 - dist/lib/rules/dialogsurface-needs-aria.d.ts | 1 - .../rules/dialogsurface-needs-aria.d.ts.map | 1 - dist/lib/rules/dropdown-needs-labelling.d.ts | 1 - .../rules/dropdown-needs-labelling.d.ts.map | 1 - dist/lib/rules/image-link-missing-aria.d.ts | 1 - .../rules/image-link-missing-aria.d.ts.map | 1 - ...ut-components-require-accessible-name.d.ts | 1 - ...omponents-require-accessible-name.d.ts.map | 1 - dist/lib/rules/menu-item-needs-labelling.d.ts | 1 - .../rules/menu-item-needs-labelling.d.ts.map | 1 - dist/lib/rules/no-empty-components.d.ts | 1 - dist/lib/rules/no-empty-components.d.ts.map | 1 - .../prefer-aria-over-title-attribute.d.ts | 8 +- .../prefer-aria-over-title-attribute.d.ts.map | 1 - .../rules/prefer-aria-over-title-attribute.js | 46 +-- .../rules/progressbar-needs-labelling.d.ts | 1 - .../progressbar-needs-labelling.d.ts.map | 1 - .../lib/rules/radio-button-missing-label.d.ts | 1 - .../rules/radio-button-missing-label.d.ts.map | 1 - dist/lib/rules/radiogroup-missing-label.d.ts | 1 - .../rules/radiogroup-missing-label.d.ts.map | 1 - .../rules/spin-button-needs-labelling.d.ts | 1 - .../spin-button-needs-labelling.d.ts.map | 1 - .../spin-button-unrecommended-labelling.d.ts | 1 - ...in-button-unrecommended-labelling.d.ts.map | 1 - dist/lib/rules/spinner-needs-labelling.d.ts | 1 - .../rules/spinner-needs-labelling.d.ts.map | 1 - dist/lib/rules/switch-needs-labelling.d.ts | 1 - .../lib/rules/switch-needs-labelling.d.ts.map | 1 - dist/lib/rules/toolbar-missing-aria.d.ts | 1 - dist/lib/rules/toolbar-missing-aria.d.ts.map | 1 - dist/lib/rules/tooltip-not-recommended.d.ts | 1 - .../rules/tooltip-not-recommended.d.ts.map | 1 - dist/lib/util/flattenChildren.d.ts | 1 - dist/lib/util/flattenChildren.d.ts.map | 1 - dist/lib/util/hasFieldParent.d.ts | 1 - dist/lib/util/hasFieldParent.d.ts.map | 1 - dist/lib/util/hasNonEmptyProp.d.ts | 1 - dist/lib/util/hasNonEmptyProp.d.ts.map | 1 - dist/lib/util/hasTextContentChild.d.ts | 1 - dist/lib/util/hasTextContentChild.d.ts.map | 1 - dist/lib/util/hasTooltipParent.d.ts | 1 - dist/lib/util/hasTooltipParent.d.ts.map | 1 - dist/lib/util/labelUtils.d.ts | 1 - dist/lib/util/labelUtils.d.ts.map | 1 - lib/{index.js => index.ts} | 5 +- lib/rules/prefer-aria-over-title-attribute.ts | 98 ++++++ ... prefer-aria-over-title-attribute.test.ts} | 19 +- tsconfig.json | 2 +- 80 files changed, 144 insertions(+), 404 deletions(-) delete mode 100644 dist/lib/applicableComponents/buttonBasedComponents.d.ts.map delete mode 100644 dist/lib/applicableComponents/inputBasedComponents.d.ts.map delete mode 100644 dist/lib/index.d.ts.map delete mode 100644 dist/lib/rules/accordion-header-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map delete mode 100644 dist/lib/rules/avatar-needs-name.d.ts.map delete mode 100644 dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map delete mode 100644 dist/lib/rules/badge-needs-accessible-name.d.ts.map delete mode 100644 dist/lib/rules/breadcrumb-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/buttons/image-button-missing-aria.d.ts.map delete mode 100644 dist/lib/rules/buttons/no-empty-buttons.d.ts.map delete mode 100644 dist/lib/rules/checkbox-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/combobox-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map delete mode 100644 dist/lib/rules/dialogsurface-needs-aria.d.ts.map delete mode 100644 dist/lib/rules/dropdown-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/image-link-missing-aria.d.ts.map delete mode 100644 dist/lib/rules/input-components-require-accessible-name.d.ts.map delete mode 100644 dist/lib/rules/menu-item-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/no-empty-components.d.ts.map delete mode 100644 dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map delete mode 100644 dist/lib/rules/progressbar-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/radio-button-missing-label.d.ts.map delete mode 100644 dist/lib/rules/radiogroup-missing-label.d.ts.map delete mode 100644 dist/lib/rules/spin-button-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map delete mode 100644 dist/lib/rules/spinner-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/switch-needs-labelling.d.ts.map delete mode 100644 dist/lib/rules/toolbar-missing-aria.d.ts.map delete mode 100644 dist/lib/rules/tooltip-not-recommended.d.ts.map delete mode 100644 dist/lib/util/flattenChildren.d.ts.map delete mode 100644 dist/lib/util/hasFieldParent.d.ts.map delete mode 100644 dist/lib/util/hasNonEmptyProp.d.ts.map delete mode 100644 dist/lib/util/hasTextContentChild.d.ts.map delete mode 100644 dist/lib/util/hasTooltipParent.d.ts.map delete mode 100644 dist/lib/util/labelUtils.d.ts.map rename lib/{index.js => index.ts} (97%) create mode 100644 lib/rules/prefer-aria-over-title-attribute.ts rename tests/lib/rules/{prefer-aria-over-title-attribute.js => prefer-aria-over-title-attribute.test.ts} (82%) diff --git a/dist/lib/applicableComponents/buttonBasedComponents.d.ts b/dist/lib/applicableComponents/buttonBasedComponents.d.ts index 3f42888..822650c 100644 --- a/dist/lib/applicableComponents/buttonBasedComponents.d.ts +++ b/dist/lib/applicableComponents/buttonBasedComponents.d.ts @@ -1,2 +1 @@ export const applicableComponents: string[]; -//# sourceMappingURL=buttonBasedComponents.d.ts.map \ No newline at end of file diff --git a/dist/lib/applicableComponents/buttonBasedComponents.d.ts.map b/dist/lib/applicableComponents/buttonBasedComponents.d.ts.map deleted file mode 100644 index 66e5a16..0000000 --- a/dist/lib/applicableComponents/buttonBasedComponents.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"buttonBasedComponents.d.ts","sourceRoot":"","sources":["../../../lib/applicableComponents/buttonBasedComponents.js"],"names":[],"mappings":"AAGA,4CAA0E"} \ No newline at end of file diff --git a/dist/lib/applicableComponents/inputBasedComponents.d.ts b/dist/lib/applicableComponents/inputBasedComponents.d.ts index 2677eb3..822650c 100644 --- a/dist/lib/applicableComponents/inputBasedComponents.d.ts +++ b/dist/lib/applicableComponents/inputBasedComponents.d.ts @@ -1,2 +1 @@ export const applicableComponents: string[]; -//# sourceMappingURL=inputBasedComponents.d.ts.map \ No newline at end of file diff --git a/dist/lib/applicableComponents/inputBasedComponents.d.ts.map b/dist/lib/applicableComponents/inputBasedComponents.d.ts.map deleted file mode 100644 index a9dbd2a..0000000 --- a/dist/lib/applicableComponents/inputBasedComponents.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"inputBasedComponents.d.ts","sourceRoot":"","sources":["../../../lib/applicableComponents/inputBasedComponents.js"],"names":[],"mappings":"AAGA,4CAA6H"} \ No newline at end of file diff --git a/dist/lib/index.d.ts b/dist/lib/index.d.ts index 7e06653..cb0ff5c 100644 --- a/dist/lib/index.d.ts +++ b/dist/lib/index.d.ts @@ -1,288 +1 @@ -export let rules: { - "checkbox-needs-labelling": { - meta: { - messages: { - noUnlabelledCheckbox: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "image-button-missing-aria": { - meta: { - messages: { - missingAriaLabel: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXElement(node: any): void; - }; - }; - "image-link-missing-aria": import("eslint").Rule.RuleModule; - "input-components-require-accessible-name": { - meta: { - messages: { - missingLabelOnInput: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "menu-item-needs-labelling": import("eslint").Rule.RuleModule; - "switch-needs-labelling": { - meta: { - messages: { - noUnlabelledSwitch: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "toolbar-missing-aria": import("eslint").Rule.RuleModule; - "combobox-needs-labelling": import("eslint").Rule.RuleModule; - "no-empty-components": import("eslint").Rule.RuleModule; - "accordion-header-needs-labelling": import("eslint").Rule.RuleModule; - "accordion-item-needs-header-and-panel": import("eslint").Rule.RuleModule; - "compound-button-needs-labelling": { - meta: { - messages: { - missingAriaLabel: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXElement(node: any): void; - }; - }; - "no-empty-buttons": { - meta: { - messages: { - noEmptyButtons: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXElement(node: any): any; - }; - }; - "spin-button-needs-labelling": { - meta: { - messages: { - noUnlabelledSpinButton: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "spin-button-unrecommended-labelling": { - meta: { - messages: { - unRecommendedlabellingSpinButton: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "breadcrumb-needs-labelling": import("eslint").Rule.RuleModule; - "dropdown-needs-labelling": { - meta: { - messages: { - missingLabelOrAriaLabeledByInDropdown: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: null; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "tooltip-not-recommended": import("eslint").Rule.RuleModule; - "avatar-needs-name": { - meta: { - messages: { - missingAriaLabel: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "radio-button-missing-label": { - meta: { - messages: { - noUnlabeledRadioButton: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "radiogroup-missing-label": { - meta: { - messages: { - noUnlabeledRadioGroup: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "prefer-aria-over-title-attribute": import("eslint").Rule.RuleModule; - "dialogbody-needs-title-content-and-actions": import("eslint").Rule.RuleModule; - "dialogsurface-needs-aria": import("eslint").Rule.RuleModule; - "spinner-needs-labelling": { - meta: { - messages: { - noUnlabelledSpinner: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; - "badge-needs-accessible-name": import("eslint").Rule.RuleModule; - "progressbar-needs-labelling": { - meta: { - messages: { - noUnlabelledProgressbar: string; - }; - type: string; - docs: { - description: string; - recommended: boolean; - url: string; - }; - schema: never[]; - }; - create(context: any): { - JSXOpeningElement(node: any): void; - }; - }; -}; -export namespace configs { - namespace recommended { - let rules_1: { - "@microsoft/fluentui-jsx-a11y/checkbox-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/image-link-missing-aria": string; - "@microsoft/fluentui-jsx-a11y/input-components-require-accessible-name": string; - "@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/switch-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/image-button-missing-aria": string; - "@microsoft/fluentui-jsx-a11y/toolbar-missing-aria": string; - "@microsoft/fluentui-jsx-a11y/combobox-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/no-empty-components": string; - "@microsoft/fluentui-jsx-a11y/accordion-header-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/accordion-item-needs-header-and-panel": string; - "@microsoft/fluentui-jsx-a11y/compound-button-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/no-empty-buttons": string; - "@microsoft/fluentui-jsx-a11y/spin-button-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/spin-button-unrecommended-labelling": string; - "@microsoft/fluentui-jsx-a11y/breadcrumb-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/dropdown-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/tooltip-not-recommended": string; - "@microsoft/fluentui-jsx-a11y/avatar-needs-name": string; - "@microsoft/fluentui-jsx-a11y/radio-button-missing-label": string; - "@microsoft/fluentui-jsx-a11y/radiogroup-missing-label": string; - "@microsoft/fluentui-jsx-a11y/prefer-aria-over-title-attribute": string; - "@microsoft/fluentui-jsx-a11y/dialogbody-needs-title-content-and-actions": string; - "@microsoft/fluentui-jsx-a11y/dialogsurface-needs-aria": string; - "@microsoft/fluentui-jsx-a11y/spinner-needs-labelling": string; - "@microsoft/fluentui-jsx-a11y/progressbar-needs-labelling": string; - }; - export { rules_1 as rules }; - } -} -export let processors: {}; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +export {}; diff --git a/dist/lib/index.d.ts.map b/dist/lib/index.d.ts.map deleted file mode 100644 index bc39b0a..0000000 --- a/dist/lib/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/index.js"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/index.js b/dist/lib/index.js index b969c49..4851f8f 100644 --- a/dist/lib/index.js +++ b/dist/lib/index.js @@ -1,7 +1,12 @@ +"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); console.log("Loading my-eslint-plugin"); +const prefer_aria_over_title_attribute_1 = __importDefault(require("./rules/prefer-aria-over-title-attribute")); //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ @@ -32,7 +37,7 @@ module.exports = { "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"), - "prefer-aria-over-title-attribute": require("./rules/prefer-aria-over-title-attribute"), + "prefer-aria-over-title-attribute": prefer_aria_over_title_attribute_1.default, "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"), diff --git a/dist/lib/rules/accordion-header-needs-labelling.d.ts b/dist/lib/rules/accordion-header-needs-labelling.d.ts index 8e7a695..de4c0ec 100644 --- a/dist/lib/rules/accordion-header-needs-labelling.d.ts +++ b/dist/lib/rules/accordion-header-needs-labelling.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=accordion-header-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/accordion-header-needs-labelling.d.ts.map b/dist/lib/rules/accordion-header-needs-labelling.d.ts.map deleted file mode 100644 index 9c1a3bf..0000000 --- a/dist/lib/rules/accordion-header-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"accordion-header-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/accordion-header-needs-labelling.js"],"names":[],"mappings":"wBAgBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts index 21c3f73..de4c0ec 100644 --- a/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts +++ b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=accordion-item-needs-header-and-panel.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map b/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map deleted file mode 100644 index e11b962..0000000 --- a/dist/lib/rules/accordion-item-needs-header-and-panel.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"accordion-item-needs-header-and-panel.d.ts","sourceRoot":"","sources":["../../../lib/rules/accordion-item-needs-header-and-panel.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/avatar-needs-name.d.ts b/dist/lib/rules/avatar-needs-name.d.ts index a89b58b..1bb0b91 100644 --- a/dist/lib/rules/avatar-needs-name.d.ts +++ b/dist/lib/rules/avatar-needs-name.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=avatar-needs-name.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/avatar-needs-name.d.ts.map b/dist/lib/rules/avatar-needs-name.d.ts.map deleted file mode 100644 index 80495d5..0000000 --- a/dist/lib/rules/avatar-needs-name.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"avatar-needs-name.d.ts","sourceRoot":"","sources":["../../../lib/rules/avatar-needs-name.js"],"names":[],"mappings":";;;;;;;;;;;;AA+BI;;EAyBC"} \ No newline at end of file diff --git a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts index f90a714..de4c0ec 100644 --- a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts +++ b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=avoid-using-aria-describedby-for-primary-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map b/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map deleted file mode 100644 index da2dc48..0000000 --- a/dist/lib/rules/avoid-using-aria-describedby-for-primary-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"avoid-using-aria-describedby-for-primary-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/avoid-using-aria-describedby-for-primary-labelling.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/badge-needs-accessible-name.d.ts b/dist/lib/rules/badge-needs-accessible-name.d.ts index c06ed2d..de4c0ec 100644 --- a/dist/lib/rules/badge-needs-accessible-name.d.ts +++ b/dist/lib/rules/badge-needs-accessible-name.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=badge-needs-accessible-name.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/badge-needs-accessible-name.d.ts.map b/dist/lib/rules/badge-needs-accessible-name.d.ts.map deleted file mode 100644 index 0d08843..0000000 --- a/dist/lib/rules/badge-needs-accessible-name.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"badge-needs-accessible-name.d.ts","sourceRoot":"","sources":["../../../lib/rules/badge-needs-accessible-name.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/breadcrumb-needs-labelling.d.ts b/dist/lib/rules/breadcrumb-needs-labelling.d.ts index 6beb885..de4c0ec 100644 --- a/dist/lib/rules/breadcrumb-needs-labelling.d.ts +++ b/dist/lib/rules/breadcrumb-needs-labelling.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=breadcrumb-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/breadcrumb-needs-labelling.d.ts.map b/dist/lib/rules/breadcrumb-needs-labelling.d.ts.map deleted file mode 100644 index 1296153..0000000 --- a/dist/lib/rules/breadcrumb-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"breadcrumb-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/breadcrumb-needs-labelling.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts index 19367c6..3f05421 100644 --- a/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts +++ b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXElement(node: any): void; }; -//# sourceMappingURL=compound-button-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map b/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map deleted file mode 100644 index eebf6de..0000000 --- a/dist/lib/rules/buttons/compound-button-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"compound-button-needs-labelling.d.ts","sourceRoot":"","sources":["../../../../lib/rules/buttons/compound-button-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AAiCI;;EAiCC"} \ No newline at end of file diff --git a/dist/lib/rules/buttons/image-button-missing-aria.d.ts b/dist/lib/rules/buttons/image-button-missing-aria.d.ts index 5bbc38e..3f05421 100644 --- a/dist/lib/rules/buttons/image-button-missing-aria.d.ts +++ b/dist/lib/rules/buttons/image-button-missing-aria.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXElement(node: any): void; }; -//# sourceMappingURL=image-button-missing-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/buttons/image-button-missing-aria.d.ts.map b/dist/lib/rules/buttons/image-button-missing-aria.d.ts.map deleted file mode 100644 index f19dbbe..0000000 --- a/dist/lib/rules/buttons/image-button-missing-aria.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"image-button-missing-aria.d.ts","sourceRoot":"","sources":["../../../../lib/rules/buttons/image-button-missing-aria.js"],"names":[],"mappings":";;;;;;;;;;;;AAmCI;;EA2CC"} \ No newline at end of file diff --git a/dist/lib/rules/buttons/no-empty-buttons.d.ts b/dist/lib/rules/buttons/no-empty-buttons.d.ts index 50dc5dd..da25e0f 100644 --- a/dist/lib/rules/buttons/no-empty-buttons.d.ts +++ b/dist/lib/rules/buttons/no-empty-buttons.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXElement(node: any): any; }; -//# sourceMappingURL=no-empty-buttons.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/buttons/no-empty-buttons.d.ts.map b/dist/lib/rules/buttons/no-empty-buttons.d.ts.map deleted file mode 100644 index f0d6a17..0000000 --- a/dist/lib/rules/buttons/no-empty-buttons.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"no-empty-buttons.d.ts","sourceRoot":"","sources":["../../../../lib/rules/buttons/no-empty-buttons.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EAmCC"} \ No newline at end of file diff --git a/dist/lib/rules/checkbox-needs-labelling.d.ts b/dist/lib/rules/checkbox-needs-labelling.d.ts index fbc53e8..72b437b 100644 --- a/dist/lib/rules/checkbox-needs-labelling.d.ts +++ b/dist/lib/rules/checkbox-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=checkbox-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/checkbox-needs-labelling.d.ts.map b/dist/lib/rules/checkbox-needs-labelling.d.ts.map deleted file mode 100644 index 3cb4077..0000000 --- a/dist/lib/rules/checkbox-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"checkbox-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/checkbox-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA2BC"} \ No newline at end of file diff --git a/dist/lib/rules/combobox-needs-labelling.d.ts b/dist/lib/rules/combobox-needs-labelling.d.ts index f4263ea..de4c0ec 100644 --- a/dist/lib/rules/combobox-needs-labelling.d.ts +++ b/dist/lib/rules/combobox-needs-labelling.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=combobox-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/combobox-needs-labelling.d.ts.map b/dist/lib/rules/combobox-needs-labelling.d.ts.map deleted file mode 100644 index a4ff66c..0000000 --- a/dist/lib/rules/combobox-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"combobox-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/combobox-needs-labelling.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts index 68601dd..de4c0ec 100644 --- a/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts +++ b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=dialogbody-needs-title-content-and-actions.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map b/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map deleted file mode 100644 index 78b29e7..0000000 --- a/dist/lib/rules/dialogbody-needs-title-content-and-actions.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"dialogbody-needs-title-content-and-actions.d.ts","sourceRoot":"","sources":["../../../lib/rules/dialogbody-needs-title-content-and-actions.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/dialogsurface-needs-aria.d.ts b/dist/lib/rules/dialogsurface-needs-aria.d.ts index 82946b7..de4c0ec 100644 --- a/dist/lib/rules/dialogsurface-needs-aria.d.ts +++ b/dist/lib/rules/dialogsurface-needs-aria.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=dialogsurface-needs-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/dialogsurface-needs-aria.d.ts.map b/dist/lib/rules/dialogsurface-needs-aria.d.ts.map deleted file mode 100644 index 509d523..0000000 --- a/dist/lib/rules/dialogsurface-needs-aria.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"dialogsurface-needs-aria.d.ts","sourceRoot":"","sources":["../../../lib/rules/dialogsurface-needs-aria.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/dropdown-needs-labelling.d.ts b/dist/lib/rules/dropdown-needs-labelling.d.ts index 90012da..e1e9aaf 100644 --- a/dist/lib/rules/dropdown-needs-labelling.d.ts +++ b/dist/lib/rules/dropdown-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=dropdown-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/dropdown-needs-labelling.d.ts.map b/dist/lib/rules/dropdown-needs-labelling.d.ts.map deleted file mode 100644 index dc59601..0000000 --- a/dist/lib/rules/dropdown-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"dropdown-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/dropdown-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA+BI;;EA6BC"} \ No newline at end of file diff --git a/dist/lib/rules/image-link-missing-aria.d.ts b/dist/lib/rules/image-link-missing-aria.d.ts index ce06f0b..de4c0ec 100644 --- a/dist/lib/rules/image-link-missing-aria.d.ts +++ b/dist/lib/rules/image-link-missing-aria.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=image-link-missing-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/image-link-missing-aria.d.ts.map b/dist/lib/rules/image-link-missing-aria.d.ts.map deleted file mode 100644 index c2faf9c..0000000 --- a/dist/lib/rules/image-link-missing-aria.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"image-link-missing-aria.d.ts","sourceRoot":"","sources":["../../../lib/rules/image-link-missing-aria.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/input-components-require-accessible-name.d.ts b/dist/lib/rules/input-components-require-accessible-name.d.ts index 1e8e19c..27bb3d1 100644 --- a/dist/lib/rules/input-components-require-accessible-name.d.ts +++ b/dist/lib/rules/input-components-require-accessible-name.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=input-components-require-accessible-name.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/input-components-require-accessible-name.d.ts.map b/dist/lib/rules/input-components-require-accessible-name.d.ts.map deleted file mode 100644 index c78ee40..0000000 --- a/dist/lib/rules/input-components-require-accessible-name.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"input-components-require-accessible-name.d.ts","sourceRoot":"","sources":["../../../lib/rules/input-components-require-accessible-name.js"],"names":[],"mappings":";;;;;;;;;;;;AAkCI;;EAyBC"} \ No newline at end of file diff --git a/dist/lib/rules/menu-item-needs-labelling.d.ts b/dist/lib/rules/menu-item-needs-labelling.d.ts index 50ba124..de4c0ec 100644 --- a/dist/lib/rules/menu-item-needs-labelling.d.ts +++ b/dist/lib/rules/menu-item-needs-labelling.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=menu-item-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/menu-item-needs-labelling.d.ts.map b/dist/lib/rules/menu-item-needs-labelling.d.ts.map deleted file mode 100644 index e03d3cf..0000000 --- a/dist/lib/rules/menu-item-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"menu-item-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/menu-item-needs-labelling.js"],"names":[],"mappings":"wBAeW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/no-empty-components.d.ts b/dist/lib/rules/no-empty-components.d.ts index d7884b0..de4c0ec 100644 --- a/dist/lib/rules/no-empty-components.d.ts +++ b/dist/lib/rules/no-empty-components.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=no-empty-components.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/no-empty-components.d.ts.map b/dist/lib/rules/no-empty-components.d.ts.map deleted file mode 100644 index 29f5bda..0000000 --- a/dist/lib/rules/no-empty-components.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"no-empty-components.d.ts","sourceRoot":"","sources":["../../../lib/rules/no-empty-components.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/prefer-aria-over-title-attribute.d.ts b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts index 23a72d6..12896b8 100644 --- a/dist/lib/rules/prefer-aria-over-title-attribute.d.ts +++ b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts @@ -1,3 +1,5 @@ -declare const _exports: import("eslint").Rule.RuleModule; -export = _exports; -//# sourceMappingURL=prefer-aria-over-title-attribute.d.ts.map \ No newline at end of file +import { TSESTree } from "@typescript-eslint/types"; +declare const rule: import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"preferAria", [], { + JSXElement(node: TSESTree.JSXElement): void; +}>; +export default rule; diff --git a/dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map b/dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map deleted file mode 100644 index 7582fa6..0000000 --- a/dist/lib/rules/prefer-aria-over-title-attribute.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"prefer-aria-over-title-attribute.d.ts","sourceRoot":"","sources":["../../../lib/rules/prefer-aria-over-title-attribute.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/prefer-aria-over-title-attribute.js b/dist/lib/rules/prefer-aria-over-title-attribute.js index 9841805..d857f91 100644 --- a/dist/lib/rules/prefer-aria-over-title-attribute.js +++ b/dist/lib/rules/prefer-aria-over-title-attribute.js @@ -1,18 +1,20 @@ +"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; -const { elementType } = require("jsx-ast-utils"); -const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils"); -var hasProp = require("jsx-ast-utils").hasProp; -const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); -const { hasToolTipParent } = require("../util/hasTooltipParent"); -const { hasTextContentChild } = require("../util/hasTextContentChild"); +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("@typescript-eslint/utils"); +const types_1 = require("@typescript-eslint/types"); +const jsx_ast_utils_1 = require("jsx-ast-utils"); +const labelUtils_1 = require("../util/labelUtils"); +const hasNonEmptyProp_1 = require("../util/hasNonEmptyProp"); +const hasTooltipParent_1 = require("../util/hasTooltipParent"); +const hasTextContentChild_1 = require("../util/hasTextContentChild"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ const applicableComponents = ["Button"]; -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = utils_1.ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], meta: { // possible error messages for the rule messages: { @@ -21,8 +23,7 @@ module.exports = { type: "suggestion", // `problem`, `suggestion`, or `layout` docs: { description: "The title attribute is not consistently read by screen readers, and its behavior can vary depending on the screen reader and the user's settings.", - recommended: true, - url: null // URL to the documentation page for this rule + recommended: "warn" }, fixable: "code", // Or `code` or `whitespace` schema: [] // Add a schema if the rule has options @@ -33,27 +34,27 @@ module.exports = { JSXElement(node) { const openingElement = node.openingElement; // if it is not a listed component, return - if (!applicableComponents.includes(elementType(openingElement))) { + if (!applicableComponents.includes((0, jsx_ast_utils_1.elementType)(openingElement))) { return; } // if it is not an icon button, return - if (!hasProp(openingElement.attributes, "icon")) { + if (!(0, jsx_ast_utils_1.hasProp)(openingElement.attributes, "icon")) { return; } // if it has a tooltip parent, return - if (hasToolTipParent(context)) { + if ((0, hasTooltipParent_1.hasToolTipParent)(context)) { return; } // if it has text content, return - if (hasTextContentChild(node)) { + if ((0, hasTextContentChild_1.hasTextContentChild)(node)) { return; } // the button has an associated label - if (hasAssociatedLabelViaAriaLabelledBy(openingElement, context)) { + if ((0, labelUtils_1.hasAssociatedLabelViaAriaLabelledBy)(openingElement, context)) { return; } - const hasAria = hasNonEmptyProp(openingElement.attributes, "aria-label"); - const hasTitle = hasNonEmptyProp(openingElement.attributes, "title"); + const hasAria = (0, hasNonEmptyProp_1.hasNonEmptyProp)(openingElement.attributes, "aria-label"); + const hasTitle = (0, hasNonEmptyProp_1.hasNonEmptyProp)(openingElement.attributes, "title"); // if it has no accessible name, report error if (hasTitle && !hasAria) { context.report({ @@ -61,9 +62,11 @@ module.exports = { messageId: `preferAria`, fix(fixer) { const attributes = openingElement.attributes; - const titleAttribute = attributes.find(attr => attr.name && attr.name.name === "title"); + const titleAttribute = attributes.find(attr => attr.type === types_1.AST_NODE_TYPES.JSXAttribute && attr.name && attr.name.name === "title"); // Generate the aria-label attribute - const ariaLabel = ` aria-label="${titleAttribute.value.value}"`; + const ariaLabel = ` aria-label="${titleAttribute && titleAttribute.type === types_1.AST_NODE_TYPES.JSXAttribute && titleAttribute.value + ? titleAttribute.value.value + : ""}"`; // Find the location to insert the new attribute const lastAttribute = attributes[attributes.length - 1]; const insertPosition = lastAttribute.range[1]; @@ -74,4 +77,5 @@ module.exports = { } }; } -}; +}); +exports.default = rule; diff --git a/dist/lib/rules/progressbar-needs-labelling.d.ts b/dist/lib/rules/progressbar-needs-labelling.d.ts index 44391aa..180690f 100644 --- a/dist/lib/rules/progressbar-needs-labelling.d.ts +++ b/dist/lib/rules/progressbar-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=progressbar-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/progressbar-needs-labelling.d.ts.map b/dist/lib/rules/progressbar-needs-labelling.d.ts.map deleted file mode 100644 index 848cf0d..0000000 --- a/dist/lib/rules/progressbar-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"progressbar-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/progressbar-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA0CC"} \ No newline at end of file diff --git a/dist/lib/rules/radio-button-missing-label.d.ts b/dist/lib/rules/radio-button-missing-label.d.ts index 5fc986b..7465799 100644 --- a/dist/lib/rules/radio-button-missing-label.d.ts +++ b/dist/lib/rules/radio-button-missing-label.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=radio-button-missing-label.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/radio-button-missing-label.d.ts.map b/dist/lib/rules/radio-button-missing-label.d.ts.map deleted file mode 100644 index 0f73246..0000000 --- a/dist/lib/rules/radio-button-missing-label.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"radio-button-missing-label.d.ts","sourceRoot":"","sources":["../../../lib/rules/radio-button-missing-label.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA4BC"} \ No newline at end of file diff --git a/dist/lib/rules/radiogroup-missing-label.d.ts b/dist/lib/rules/radiogroup-missing-label.d.ts index 67d6666..aab1b22 100644 --- a/dist/lib/rules/radiogroup-missing-label.d.ts +++ b/dist/lib/rules/radiogroup-missing-label.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=radiogroup-missing-label.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/radiogroup-missing-label.d.ts.map b/dist/lib/rules/radiogroup-missing-label.d.ts.map deleted file mode 100644 index 5a20574..0000000 --- a/dist/lib/rules/radiogroup-missing-label.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"radiogroup-missing-label.d.ts","sourceRoot":"","sources":["../../../lib/rules/radiogroup-missing-label.js"],"names":[],"mappings":";;;;;;;;;;;;AAgCI;;EA4BC"} \ No newline at end of file diff --git a/dist/lib/rules/spin-button-needs-labelling.d.ts b/dist/lib/rules/spin-button-needs-labelling.d.ts index 628b05d..c49d72a 100644 --- a/dist/lib/rules/spin-button-needs-labelling.d.ts +++ b/dist/lib/rules/spin-button-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=spin-button-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/spin-button-needs-labelling.d.ts.map b/dist/lib/rules/spin-button-needs-labelling.d.ts.map deleted file mode 100644 index 5437d0b..0000000 --- a/dist/lib/rules/spin-button-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"spin-button-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/spin-button-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA8BI;;EA0BC"} \ No newline at end of file diff --git a/dist/lib/rules/spin-button-unrecommended-labelling.d.ts b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts index a66d49c..7fcb4f0 100644 --- a/dist/lib/rules/spin-button-unrecommended-labelling.d.ts +++ b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=spin-button-unrecommended-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map b/dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map deleted file mode 100644 index cfca0ab..0000000 --- a/dist/lib/rules/spin-button-unrecommended-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"spin-button-unrecommended-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/spin-button-unrecommended-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA8BI;;EAkBC"} \ No newline at end of file diff --git a/dist/lib/rules/spinner-needs-labelling.d.ts b/dist/lib/rules/spinner-needs-labelling.d.ts index 069b916..30b60ae 100644 --- a/dist/lib/rules/spinner-needs-labelling.d.ts +++ b/dist/lib/rules/spinner-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=spinner-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/spinner-needs-labelling.d.ts.map b/dist/lib/rules/spinner-needs-labelling.d.ts.map deleted file mode 100644 index 0a8d190..0000000 --- a/dist/lib/rules/spinner-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"spinner-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/spinner-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA6BI;;EAuBC"} \ No newline at end of file diff --git a/dist/lib/rules/switch-needs-labelling.d.ts b/dist/lib/rules/switch-needs-labelling.d.ts index de45a22..8e61b45 100644 --- a/dist/lib/rules/switch-needs-labelling.d.ts +++ b/dist/lib/rules/switch-needs-labelling.d.ts @@ -13,4 +13,3 @@ export namespace meta { export function create(context: any): { JSXOpeningElement(node: any): void; }; -//# sourceMappingURL=switch-needs-labelling.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/switch-needs-labelling.d.ts.map b/dist/lib/rules/switch-needs-labelling.d.ts.map deleted file mode 100644 index 7dccf88..0000000 --- a/dist/lib/rules/switch-needs-labelling.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"switch-needs-labelling.d.ts","sourceRoot":"","sources":["../../../lib/rules/switch-needs-labelling.js"],"names":[],"mappings":";;;;;;;;;;;;AA+BI;;EA2BC"} \ No newline at end of file diff --git a/dist/lib/rules/toolbar-missing-aria.d.ts b/dist/lib/rules/toolbar-missing-aria.d.ts index c026736..de4c0ec 100644 --- a/dist/lib/rules/toolbar-missing-aria.d.ts +++ b/dist/lib/rules/toolbar-missing-aria.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=toolbar-missing-aria.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/toolbar-missing-aria.d.ts.map b/dist/lib/rules/toolbar-missing-aria.d.ts.map deleted file mode 100644 index 15a5f74..0000000 --- a/dist/lib/rules/toolbar-missing-aria.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"toolbar-missing-aria.d.ts","sourceRoot":"","sources":["../../../lib/rules/toolbar-missing-aria.js"],"names":[],"mappings":"wBAaW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/rules/tooltip-not-recommended.d.ts b/dist/lib/rules/tooltip-not-recommended.d.ts index 39af2fb..de4c0ec 100644 --- a/dist/lib/rules/tooltip-not-recommended.d.ts +++ b/dist/lib/rules/tooltip-not-recommended.d.ts @@ -1,3 +1,2 @@ declare const _exports: import("eslint").Rule.RuleModule; export = _exports; -//# sourceMappingURL=tooltip-not-recommended.d.ts.map \ No newline at end of file diff --git a/dist/lib/rules/tooltip-not-recommended.d.ts.map b/dist/lib/rules/tooltip-not-recommended.d.ts.map deleted file mode 100644 index 9e729d8..0000000 --- a/dist/lib/rules/tooltip-not-recommended.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"tooltip-not-recommended.d.ts","sourceRoot":"","sources":["../../../lib/rules/tooltip-not-recommended.js"],"names":[],"mappings":"wBAcW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/dist/lib/util/flattenChildren.d.ts b/dist/lib/util/flattenChildren.d.ts index 44d402a..fec9578 100644 --- a/dist/lib/util/flattenChildren.d.ts +++ b/dist/lib/util/flattenChildren.d.ts @@ -1,2 +1 @@ export function flattenChildren(node: any): any[]; -//# sourceMappingURL=flattenChildren.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/flattenChildren.d.ts.map b/dist/lib/util/flattenChildren.d.ts.map deleted file mode 100644 index 8952eee..0000000 --- a/dist/lib/util/flattenChildren.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"flattenChildren.d.ts","sourceRoot":"","sources":["../../../lib/util/flattenChildren.js"],"names":[],"mappings":"AAIA,kDAcC"} \ No newline at end of file diff --git a/dist/lib/util/hasFieldParent.d.ts b/dist/lib/util/hasFieldParent.d.ts index 5920a89..f2ad2e4 100644 --- a/dist/lib/util/hasFieldParent.d.ts +++ b/dist/lib/util/hasFieldParent.d.ts @@ -1,2 +1 @@ export function hasFieldParent(context: any): boolean; -//# sourceMappingURL=hasFieldParent.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasFieldParent.d.ts.map b/dist/lib/util/hasFieldParent.d.ts.map deleted file mode 100644 index f1a56e4..0000000 --- a/dist/lib/util/hasFieldParent.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"hasFieldParent.d.ts","sourceRoot":"","sources":["../../../lib/util/hasFieldParent.js"],"names":[],"mappings":"AAKA,sDAqBC"} \ No newline at end of file diff --git a/dist/lib/util/hasNonEmptyProp.d.ts b/dist/lib/util/hasNonEmptyProp.d.ts index 7409085..dc1ddb1 100644 --- a/dist/lib/util/hasNonEmptyProp.d.ts +++ b/dist/lib/util/hasNonEmptyProp.d.ts @@ -5,4 +5,3 @@ * @returns boolean */ export function hasNonEmptyProp(attributes: any, name: any): boolean; -//# sourceMappingURL=hasNonEmptyProp.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasNonEmptyProp.d.ts.map b/dist/lib/util/hasNonEmptyProp.d.ts.map deleted file mode 100644 index e12ab45..0000000 --- a/dist/lib/util/hasNonEmptyProp.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"hasNonEmptyProp.d.ts","sourceRoot":"","sources":["../../../lib/util/hasNonEmptyProp.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,4CAJW,GAAC,QACD,GAAC,WAmBX"} \ No newline at end of file diff --git a/dist/lib/util/hasTextContentChild.d.ts b/dist/lib/util/hasTextContentChild.d.ts index bf5f42d..7be2576 100644 --- a/dist/lib/util/hasTextContentChild.d.ts +++ b/dist/lib/util/hasTextContentChild.d.ts @@ -4,4 +4,3 @@ * @returns boolean */ export function hasTextContentChild(node: any): boolean; -//# sourceMappingURL=hasTextContentChild.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasTextContentChild.d.ts.map b/dist/lib/util/hasTextContentChild.d.ts.map deleted file mode 100644 index 5b25d75..0000000 --- a/dist/lib/util/hasTextContentChild.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"hasTextContentChild.d.ts","sourceRoot":"","sources":["../../../lib/util/hasTextContentChild.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,0CAHW,GAAC,WAcX"} \ No newline at end of file diff --git a/dist/lib/util/hasTooltipParent.d.ts b/dist/lib/util/hasTooltipParent.d.ts index 1155136..150c885 100644 --- a/dist/lib/util/hasTooltipParent.d.ts +++ b/dist/lib/util/hasTooltipParent.d.ts @@ -1,2 +1 @@ export function hasToolTipParent(context: any): boolean; -//# sourceMappingURL=hasTooltipParent.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/hasTooltipParent.d.ts.map b/dist/lib/util/hasTooltipParent.d.ts.map deleted file mode 100644 index a7f10d0..0000000 --- a/dist/lib/util/hasTooltipParent.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"hasTooltipParent.d.ts","sourceRoot":"","sources":["../../../lib/util/hasTooltipParent.js"],"names":[],"mappings":"AAKA,wDAqBC"} \ No newline at end of file diff --git a/dist/lib/util/labelUtils.d.ts b/dist/lib/util/labelUtils.d.ts index 362acd5..19e994a 100644 --- a/dist/lib/util/labelUtils.d.ts +++ b/dist/lib/util/labelUtils.d.ts @@ -69,4 +69,3 @@ export function hasAssociatedLabelViaAriaDescribedby(openingElement: any, contex * @returns boolean for match found or not. */ export function hasAssociatedAriaText(openingElement: any, context: any, ariaAttribute: any): boolean; -//# sourceMappingURL=labelUtils.d.ts.map \ No newline at end of file diff --git a/dist/lib/util/labelUtils.d.ts.map b/dist/lib/util/labelUtils.d.ts.map deleted file mode 100644 index 801a5e4..0000000 --- a/dist/lib/util/labelUtils.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"labelUtils.d.ts","sourceRoot":"","sources":["../../../lib/util/labelUtils.js"],"names":[],"mappings":"AAQA;;;;;;;;;GASG;AACH,0CAHW,GAAC,OAUX;AAED;;;;;;;GAOG;AACH,+CAJW,GAAC,WACD,GAAC,WAWX;AAED;;;;;;;GAOG;AACH,4CAJW,GAAC,WACD,GAAC,WAYX;AAED;;;;;;;;GAQG;AACH,oEAJW,GAAC,WACD,GAAC,WASX;AAmBD;;;;;;;;GAQG;AACH,6DAJW,GAAC,WACD,GAAC,WAOX;AA9BD;;;;;;;;GAQG;AACH,qEAJW,GAAC,WACD,GAAC,WASX;AAiBD;;;;;;;;;;GAUG;AACH,sDALW,GAAC,WACD,GAAC,iBACD,GAAC,WAqBX"} \ No newline at end of file diff --git a/lib/index.js b/lib/index.ts similarity index 97% rename from lib/index.js rename to lib/index.ts index 56f3198..6ab7ae1 100644 --- a/lib/index.js +++ b/lib/index.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; console.log("Loading my-eslint-plugin"); +import preferAriaOverTitleAttribute from "./rules/prefer-aria-over-title-attribute"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ @@ -35,7 +35,7 @@ module.exports = { "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"), - "prefer-aria-over-title-attribute": require("./rules/prefer-aria-over-title-attribute"), + "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"), @@ -80,4 +80,3 @@ module.exports = { module.exports.processors = { // add your processors here }; - diff --git a/lib/rules/prefer-aria-over-title-attribute.ts b/lib/rules/prefer-aria-over-title-attribute.ts new file mode 100644 index 0000000..1afa654 --- /dev/null +++ b/lib/rules/prefer-aria-over-title-attribute.ts @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { ESLintUtils } from "@typescript-eslint/utils"; +import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/types"; +import { elementType, hasProp } from "jsx-ast-utils"; +import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils"; +import { hasNonEmptyProp } from "../util/hasNonEmptyProp"; +import { hasToolTipParent } from "../util/hasTooltipParent"; +import { hasTextContentChild } from "../util/hasTextContentChild"; +import { JSXAttribute, JSXOpeningElement, Literal } from "estree-jsx"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const applicableComponents = ["Button"]; + +const rule = ESLintUtils.RuleCreator.withoutDocs({ + defaultOptions: [], + meta: { + // possible error messages for the rule + messages: { + preferAria: `Prefer aria over the title attribute for accessible labelling: ${applicableComponents.join(", ")}` + }, + type: "suggestion", // `problem`, `suggestion`, or `layout` + docs: { + description: + "The title attribute is not consistently read by screen readers, and its behavior can vary depending on the screen reader and the user's settings.", + recommended: "warn" + }, + fixable: "code", // 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: TSESTree.JSXElement) { + const openingElement = node.openingElement; + // if it is not a listed component, return + if (!applicableComponents.includes(elementType(openingElement as JSXOpeningElement))) { + return; + } + // if it is not an icon button, return + if (!hasProp(openingElement.attributes as JSXAttribute[], "icon")) { + return; + } + + // if it has a tooltip parent, return + if (hasToolTipParent(context)) { + return; + } + + // if it has text content, return + if (hasTextContentChild(node)) { + return; + } + + // the button has an associated label + if (hasAssociatedLabelViaAriaLabelledBy(openingElement, context)) { + return; + } + + const hasAria = hasNonEmptyProp(openingElement.attributes, "aria-label"); + const hasTitle = hasNonEmptyProp(openingElement.attributes, "title"); + + // if it has no accessible name, report error + if (hasTitle && !hasAria) { + context.report({ + node, + messageId: `preferAria`, + fix(fixer) { + const attributes = openingElement.attributes; + const titleAttribute = attributes.find( + attr => attr.type === AST_NODE_TYPES.JSXAttribute && attr.name && attr.name.name === "title" + ); + // Generate the aria-label attribute + const ariaLabel = ` aria-label="${ + titleAttribute && titleAttribute.type === AST_NODE_TYPES.JSXAttribute && titleAttribute.value + ? (titleAttribute.value as Literal).value + : "" + }"`; + + // Find the location to insert the new attribute + const lastAttribute = attributes[attributes.length - 1]; + const insertPosition = lastAttribute.range[1]; + + return fixer.insertTextAfterRange([insertPosition, insertPosition], ariaLabel); + } + }); + } + } + }; + } +}); + +export default rule; diff --git a/tests/lib/rules/prefer-aria-over-title-attribute.js b/tests/lib/rules/prefer-aria-over-title-attribute.test.ts similarity index 82% rename from tests/lib/rules/prefer-aria-over-title-attribute.js rename to tests/lib/rules/prefer-aria-over-title-attribute.test.ts index 3a90922..e1c8dbc 100644 --- a/tests/lib/rules/prefer-aria-over-title-attribute.js +++ b/tests/lib/rules/prefer-aria-over-title-attribute.test.ts @@ -1,28 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -"use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/prefer-aria-over-title-attribute"), - RuleTester = require("eslint").RuleTester; -RuleTester.setDefaultConfig({ - parserOptions: { - ecmaVersion: 6, - ecmaFeatures: { - jsx: true - } - } -}); +import { RuleTester } from "eslint"; +import rule from "../../../lib/rules/prefer-aria-over-title-attribute"; + //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); -ruleTester.run("prefer-aria-over-title-attribute", rule, { +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); +ruleTester.run("prefer-aria-over-title-attribute", rule as any, { valid: [ // give me some code that won't trigger a warning '