diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..abe810c5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +__fixtures__/ +dist/ diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 8ca265e0..5468e6d0 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.6.0 + uses: dependabot/fetch-metadata@v2.4.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 20b75ff5..39ce9123 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,11 +10,11 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: yarn @@ -27,7 +27,7 @@ jobs: # https://github.com/crazy-max/ghaction-github-pages - name: Deploy to GitHub Pages - uses: crazy-max/ghaction-github-pages@v3 + uses: crazy-max/ghaction-github-pages@v4 with: target_branch: gh-pages build_dir: docs/.vitepress/dist diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 23092e63..896807e1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,14 +12,15 @@ jobs: CI: true steps: - uses: actions/checkout@master - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: yarn - name: Lint and test run: | yarn install --frozen-lockfile - yarn prepublishOnly + yarn build yarn lint yarn prettier --check '**/*' yarn test + yarn test:integration diff --git a/.gitignore b/.gitignore index aab752e0..9a0c8974 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /.husky/ /.idea/ /dist/ -/node_modules/ +node_modules /docs/.vitepress/dist /docs/.vitepress/cache yarn-error.log diff --git a/.prettierignore b/.prettierignore index 1559e0d8..3f7e7daa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ .eslintcache +.eslintignore .gitignore .husky .npmignore @@ -6,3 +7,4 @@ LICENSE yarn.lock dist +__fixtures__ diff --git a/__tests__/integrations/__fixtures__/flat-config/.npmrc b/__tests__/integrations/__fixtures__/flat-config/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/__tests__/integrations/__fixtures__/flat-config/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/__tests__/integrations/__fixtures__/flat-config/a.vue b/__tests__/integrations/__fixtures__/flat-config/a.vue new file mode 100644 index 00000000..6f0b8f75 --- /dev/null +++ b/__tests__/integrations/__fixtures__/flat-config/a.vue @@ -0,0 +1,11 @@ + + + diff --git a/__tests__/integrations/__fixtures__/flat-config/eslint.config.js b/__tests__/integrations/__fixtures__/flat-config/eslint.config.js new file mode 100644 index 00000000..5ae8ab92 --- /dev/null +++ b/__tests__/integrations/__fixtures__/flat-config/eslint.config.js @@ -0,0 +1,10 @@ +import plugin from "eslint-plugin-vuejs-accessibility"; + +export default [ + ...plugin.configs["flat/recommended"], + { + rules: { + "vuejs-accessibility/alt-text": "warn" + } + } +]; diff --git a/__tests__/integrations/__fixtures__/flat-config/package.json b/__tests__/integrations/__fixtures__/flat-config/package.json new file mode 100644 index 00000000..ce85990d --- /dev/null +++ b/__tests__/integrations/__fixtures__/flat-config/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "name": "integration-test-for-flat-config", + "type": "module", + "version": "1.0.0", + "description": "Integration test for flat config", + "dependencies": { + "eslint": "^9.0.0", + "eslint-plugin-vuejs-accessibility": "file:../../../.." + } +} diff --git a/__tests__/integrations/__fixtures__/legacy-config/.eslintrc.json b/__tests__/integrations/__fixtures__/legacy-config/.eslintrc.json new file mode 100644 index 00000000..b8124b09 --- /dev/null +++ b/__tests__/integrations/__fixtures__/legacy-config/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "root": true, + "extends": ["plugin:vuejs-accessibility/recommended"], + "plugins": ["vuejs-accessibility"], + "parser": "vue-eslint-parser", + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2019 + }, + "rules": { + "vuejs-accessibility/alt-text": "warn" + } +} diff --git a/__tests__/integrations/__fixtures__/legacy-config/.npmrc b/__tests__/integrations/__fixtures__/legacy-config/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/__tests__/integrations/__fixtures__/legacy-config/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/__tests__/integrations/__fixtures__/legacy-config/a.vue b/__tests__/integrations/__fixtures__/legacy-config/a.vue new file mode 100644 index 00000000..6f0b8f75 --- /dev/null +++ b/__tests__/integrations/__fixtures__/legacy-config/a.vue @@ -0,0 +1,11 @@ + + + diff --git a/__tests__/integrations/__fixtures__/legacy-config/package.json b/__tests__/integrations/__fixtures__/legacy-config/package.json new file mode 100644 index 00000000..34d02ee5 --- /dev/null +++ b/__tests__/integrations/__fixtures__/legacy-config/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "name": "integration-test-for-legacy-config", + "type": "module", + "version": "1.0.0", + "description": "Integration test for legacy config", + "dependencies": { + "eslint": "^8.57.0-0", + "eslint-plugin-vuejs-accessibility": "file:../../../.." + } +} diff --git a/__tests__/integrations/flat-config.spec.ts b/__tests__/integrations/flat-config.spec.ts new file mode 100644 index 00000000..e4998163 --- /dev/null +++ b/__tests__/integrations/flat-config.spec.ts @@ -0,0 +1,40 @@ +import cp from "child_process"; +import path from "path"; +import semver from "semver"; +import { readPackageJson } from "./helper"; + +const ESLINT = `.${path.sep}node_modules${path.sep}.bin${path.sep}eslint`; + +describe("Integration with flat config", () => { + let originalCwd: null | string = null; + const dirFixture = path.join(__dirname, "__fixtures__/flat-config"); + + beforeEach(() => { + originalCwd = process.cwd(); + process.chdir(dirFixture); + cp.execSync("npm i -f", { stdio: "inherit" }); + }); + afterEach(() => { + originalCwd && process.chdir(originalCwd); + }); + + it("should work with config", () => { + expect.assertions(2); + + const eslintPackageJson = readPackageJson( + path.resolve(dirFixture, "node_modules/eslint") + ); + + if (!semver.satisfies(process.version, eslintPackageJson.engines.node)) { + return; + } + + const result = JSON.parse( + cp.execSync(`${ESLINT} a.vue --max-warnings 1 --format=json`, { + encoding: "utf-8" + }) + ); + expect(result.length).toBe(1); + expect(result[0].messages[0].messageId).toBe("imgMissingAlt"); + }); +}); diff --git a/__tests__/integrations/helper.ts b/__tests__/integrations/helper.ts new file mode 100644 index 00000000..d69995d1 --- /dev/null +++ b/__tests__/integrations/helper.ts @@ -0,0 +1,8 @@ +import fs from "fs"; +import path from "path"; + +export function readPackageJson(base: string) { + return JSON.parse( + fs.readFileSync(path.resolve(base, "package.json"), "utf-8") + ); +} diff --git a/__tests__/integrations/legacy-config.spec.ts b/__tests__/integrations/legacy-config.spec.ts new file mode 100644 index 00000000..a7570d0d --- /dev/null +++ b/__tests__/integrations/legacy-config.spec.ts @@ -0,0 +1,40 @@ +import cp from "child_process"; +import path from "path"; +import semver from "semver"; +import { readPackageJson } from "./helper"; + +const ESLINT = `.${path.sep}node_modules${path.sep}.bin${path.sep}eslint`; + +describe("Integration with legacy config", () => { + let originalCwd: null | string = null; + const dirFixture = path.join(__dirname, "__fixtures__/legacy-config"); + + beforeEach(() => { + originalCwd = process.cwd(); + process.chdir(dirFixture); + cp.execSync("npm i -f", { stdio: "inherit" }); + }); + afterEach(() => { + originalCwd && process.chdir(originalCwd); + }); + + it("should work with config", () => { + expect.assertions(2); + + const eslintPackageJson = readPackageJson( + path.resolve(dirFixture, "node_modules/eslint") + ); + + if (!semver.satisfies(process.version, eslintPackageJson.engines.node)) { + return; + } + + const result = JSON.parse( + cp.execSync(`${ESLINT} a.vue --max-warnings 1 --format=json`, { + encoding: "utf-8" + }) + ); + expect(result.length).toBe(1); + expect(result[0].messages[0].messageId).toBe("imgMissingAlt"); + }); +}); diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.mts similarity index 100% rename from docs/.vitepress/config.ts rename to docs/.vitepress/config.mts diff --git a/docs/index.md b/docs/index.md index 74073a93..7b0f8711 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,33 @@ pnpm add -D eslint-plugin-vuejs-accessibility ## 📖 Usage -Add `vuejs-accessibility` to the plugins section of your `eslint` configuration. You can omit the `eslint-plugin-` prefix: +### Configuration (`eslint.config.js`) + +Use `eslint.config.js` file to configure rules. This is the default in ESLint v9, but can be used starting from ESLint v8.57.0. See also: https://eslint.org/docs/latest/use/configure/configuration-files-new. + +Example eslint.config.js: + +```js +import pluginVueA11y from "eslint-plugin-vuejs-accessibility"; + +export default [ + // add more generic rulesets here, such as: + // js.configs.recommended, + ...pluginVueA11y.configs["flat/recommended"], + { + rules: { + // override/add rules settings here, such as: + // "vuejs-accessibility/alt-text": "error" + } + } +]; +``` + +### Configuration (`.eslintrc`) + +Use `.eslintrc.*` file to configure rules in ESLint < v9. See also: https://eslint.org/docs/latest/use/configure/. + +Add `vuejs-accessibility` to the plugins section of your configuration. You can omit the `eslint-plugin-` prefix: ```json { diff --git a/docs/rule-overview/rule-overview.data.ts b/docs/rule-overview/rule-overview.data.mts similarity index 63% rename from docs/rule-overview/rule-overview.data.ts rename to docs/rule-overview/rule-overview.data.mts index ca67c9ed..172a93be 100644 --- a/docs/rule-overview/rule-overview.data.ts +++ b/docs/rule-overview/rule-overview.data.mts @@ -1,6 +1,6 @@ import { defineLoader } from "vitepress"; -import recommended from "../../src/configs/recommended"; -import { rules } from "../.vitepress/rulesForSidebar"; +import { rules as baseRules } from "../../src/configs/rules.js"; +import { rules } from "../.vitepress/rulesForSidebar.js"; export type Data = Array<{ name: string; @@ -16,15 +16,15 @@ export default defineLoader({ const recommended = getRecommendedRules(); return rules.map((rule) => ({ name: rule.text, - link: rule.link, + link: formatRuleLink(rule.link), recommended: recommended.includes(rule.text) })); } }); function getRecommendedRules() { - if (recommended.rules) { - return Object.keys(recommended.rules).map(removeRulePrefix); + if (baseRules) { + return Object.keys(baseRules).map(removeRulePrefix); } return []; } @@ -32,3 +32,7 @@ function getRecommendedRules() { function removeRulePrefix(ruleName: string) { return ruleName.replace("vuejs-accessibility/", ""); } + +function formatRuleLink(ruleLink: string) { + return "..".concat(ruleLink); +} diff --git a/docs/rules/no-aria-hidden-on-focusable.md b/docs/rules/no-aria-hidden-on-focusable.md new file mode 100644 index 00000000..2c340b9d --- /dev/null +++ b/docs/rules/no-aria-hidden-on-focusable.md @@ -0,0 +1,79 @@ +# no-aria-hidden-on-focusable + +Enforce that `aria-hidden="true"` is not set on focusable elements or parent of focusable elements. + +`aria-hidden="true"` can be used to hide purely decorative content from screen reader users. An element with `aria-hidden="true"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `aria-hidden="true"` on focusable elements. + +See more in [WAI-ARIA Use in HTML](https://www.w3.org/TR/using-aria/#fourth). + +### ✔ Succeed + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +### ❌ Fail + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` diff --git a/docs/rules/no-role-presentation-on-focusable.md b/docs/rules/no-role-presentation-on-focusable.md new file mode 100644 index 00000000..21b4177f --- /dev/null +++ b/docs/rules/no-role-presentation-on-focusable.md @@ -0,0 +1,79 @@ +# no-role-presentation-on-focusable + +Enforce that `role="presentation"` is not set on focusable elements or parent of focusbale elements. + +`role="presentation` can be used to hide purely decorative content from screen reader users. An element with `role="presentation"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `role="presentation"` on focusable elements. + +See more in [WAI-ARIA Use in HTML](https://www.w3.org/TR/using-aria/#fourth). + +### ✔ Succeed + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +### ❌ Fail + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` diff --git a/package.json b/package.json index b7b0e1ea..2128cb30 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,17 @@ { "name": "eslint-plugin-vuejs-accessibility", - "version": "2.2.0", + "version": "2.4.1", "description": "An eslint plugin for checking Vue.js files for accessibility", "main": "dist/index.js", + "files": [ + "dist" + ], "scripts": { "lint": "eslint --cache .", + "build": "tsc -p tsconfig.build.json", "prepublishOnly": "tsc -p tsconfig.build.json", "test": "jest", + "test:integration": "jest --testTimeout 60000 --testRegex \".*\\.spec\\.ts$\"", "release": "np", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs" @@ -30,31 +35,34 @@ "vuejs" ], "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" }, "dependencies": { "aria-query": "^5.3.0", "emoji-regex": "^10.0.0", - "vue-eslint-parser": "^9.0.1" + "vue-eslint-parser": "^10.1.1" }, "devDependencies": { "@types/aria-query": "^5.0.0", - "@types/eslint-scope": "^3.7.2", - "@types/jest": "^29.2.5", - "@types/node": "^20.1.0", - "@typescript-eslint/eslint-plugin": "^5.10.2", - "@typescript-eslint/parser": "^5.10.2", + "@types/eslint-scope": "^8.3.0", + "@types/jest": "^30.0.0", + "@types/node": "^24.0.0", + "@types/semver": "^7.5.7", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.8.0", - "eslint-plugin-eslint-plugin": "^5.0.0", - "husky": "^8.0.1", - "jest": "^29.2.2", - "np": "^8.0.4", + "eslint-plugin-eslint-plugin": "^6.0.0", + "globals": "^16.0.0", + "husky": "^9.0.5", + "jest": "^30.0.0", + "np": "^10.0.4", "prettier": "^3.0.0", - "pretty-quick": "^3.1.3", + "pretty-quick": "^4.0.0", + "semver": "^7.6.0", "ts-jest": "^29.0.3", "ts-node": "^10.3.0", - "typescript": "^5.0.2", - "vitepress": "1.0.0-beta.7" + "typescript": "5.9.2", + "vitepress": "1.6.4" }, "eslintConfig": { "env": { @@ -77,9 +85,6 @@ "@typescript-eslint/no-explicit-any": "off" } }, - "eslintIgnore": [ - "dist" - ], "husky": { "hooks": { "pre-commit": "pretty-quick --staged" diff --git a/src/configs/flat/recommended.ts b/src/configs/flat/recommended.ts new file mode 100644 index 00000000..acdcfa0e --- /dev/null +++ b/src/configs/flat/recommended.ts @@ -0,0 +1,35 @@ +import type { Linter } from "eslint"; +import globals from "globals"; +import { rules } from "../rules"; + +const recommended = [ + { + name: "vuejs-accessibility:setup:base", + plugins: { + get "vuejs-accessibility"() { + return require("../../index"); + } + }, + languageOptions: { + sourceType: "module", + globals: globals.browser + } + }, + { + name: "vuejs-accessibility:setup:with-files-rules-and-parser", + files: ["*.vue", "**/*.vue"], + plugins: { + get "vuejs-accessibility"() { + return require("../../index"); + } + }, + languageOptions: { + parser: require("vue-eslint-parser"), + sourceType: "module", + globals: globals.browser + }, + rules + } +] satisfies Linter.FlatConfig[]; + +export = recommended; diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 47ec21e1..fa9a1bd0 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -1,6 +1,7 @@ import type { Linter } from "eslint"; +import { rules } from "./rules"; -const recommended: Linter.BaseConfig = { +const recommended = { parser: require.resolve("vue-eslint-parser"), parserOptions: { ecmaVersion: 2020, @@ -11,28 +12,7 @@ const recommended: Linter.BaseConfig = { es6: true }, plugins: ["vuejs-accessibility"], - rules: { - "vuejs-accessibility/alt-text": "error", - "vuejs-accessibility/anchor-has-content": "error", - "vuejs-accessibility/aria-props": "error", - "vuejs-accessibility/aria-role": "error", - "vuejs-accessibility/aria-unsupported-elements": "error", - "vuejs-accessibility/click-events-have-key-events": "error", - "vuejs-accessibility/form-control-has-label": "error", - "vuejs-accessibility/heading-has-content": "error", - "vuejs-accessibility/iframe-has-title": "error", - "vuejs-accessibility/interactive-supports-focus": "error", - "vuejs-accessibility/label-has-for": "error", - "vuejs-accessibility/media-has-caption": "error", - "vuejs-accessibility/mouse-events-have-key-events": "error", - "vuejs-accessibility/no-access-key": "error", - "vuejs-accessibility/no-autofocus": "error", - "vuejs-accessibility/no-distracting-elements": "error", - "vuejs-accessibility/no-redundant-roles": "error", - "vuejs-accessibility/no-static-element-interactions": "error", - "vuejs-accessibility/role-has-required-aria-props": "error", - "vuejs-accessibility/tabindex-no-positive": "error" - } -}; + rules +} satisfies Linter.BaseConfig; export default recommended; diff --git a/src/configs/rules.ts b/src/configs/rules.ts new file mode 100644 index 00000000..133c96a1 --- /dev/null +++ b/src/configs/rules.ts @@ -0,0 +1,26 @@ +import type { Linter } from "eslint"; + +const rules = { + "vuejs-accessibility/alt-text": "error", + "vuejs-accessibility/anchor-has-content": "error", + "vuejs-accessibility/aria-props": "error", + "vuejs-accessibility/aria-role": "error", + "vuejs-accessibility/aria-unsupported-elements": "error", + "vuejs-accessibility/click-events-have-key-events": "error", + "vuejs-accessibility/form-control-has-label": "error", + "vuejs-accessibility/heading-has-content": "error", + "vuejs-accessibility/iframe-has-title": "error", + "vuejs-accessibility/interactive-supports-focus": "error", + "vuejs-accessibility/label-has-for": "error", + "vuejs-accessibility/media-has-caption": "error", + "vuejs-accessibility/mouse-events-have-key-events": "error", + "vuejs-accessibility/no-access-key": "error", + "vuejs-accessibility/no-autofocus": "error", + "vuejs-accessibility/no-distracting-elements": "error", + "vuejs-accessibility/no-redundant-roles": "error", + "vuejs-accessibility/no-static-element-interactions": "error", + "vuejs-accessibility/role-has-required-aria-props": "error", + "vuejs-accessibility/tabindex-no-positive": "error" +} satisfies Linter.RulesRecord; + +export { rules }; diff --git a/src/index.ts b/src/index.ts index 32c75c79..f0886ebc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -import recommended from "./configs/recommended"; +import { default as recommended } from "./configs/recommended"; +import flatRecommended from "./configs/flat/recommended"; import altText from "./rules/alt-text"; import anchorHasContent from "./rules/anchor-has-content"; @@ -14,41 +15,47 @@ import labelHasFor from "./rules/label-has-for"; import mediaHasCaption from "./rules/media-has-caption"; import mouseEventsHaveKeyEvents from "./rules/mouse-events-have-key-events"; import noAccessKey from "./rules/no-access-key"; +import noAriaHiddenOnFocusable from "./rules/no-aria-hidden-on-focusable"; import noAutofocus from "./rules/no-autofocus"; import noDistractingElements from "./rules/no-distracting-elements"; import noOnchange from "./rules/no-onchange"; import noRedundantRoles from "./rules/no-redundant-roles"; +import noRolePresentationOnFocusable from "./rules/no-role-presentation-on-focusable"; import noStaticElementInteractions from "./rules/no-static-element-interactions"; import roleHasRequiredAriaProps from "./rules/role-has-required-aria-props"; import tabindexNoPositive from "./rules/tabindex-no-positive"; -const plugin = { - configs: { - recommended - }, - rules: { - "alt-text": altText, - "anchor-has-content": anchorHasContent, - "aria-props": ariaProps, - "aria-role": ariaRole, - "aria-unsupported-elements": ariaUnsupportedElements, - "click-events-have-key-events": clickEventsHaveKeyEvents, - "form-control-has-label": formControlHasLabel, - "heading-has-content": headingHasContent, - "iframe-has-title": iframeHasTitle, - "interactive-supports-focus": interactiveSupportsFocus, - "label-has-for": labelHasFor, - "media-has-caption": mediaHasCaption, - "mouse-events-have-key-events": mouseEventsHaveKeyEvents, - "no-access-key": noAccessKey, - "no-autofocus": noAutofocus, - "no-distracting-elements": noDistractingElements, - "no-onchange": noOnchange, - "no-redundant-roles": noRedundantRoles, - "no-static-element-interactions": noStaticElementInteractions, - "role-has-required-aria-props": roleHasRequiredAriaProps, - "tabindex-no-positive": tabindexNoPositive - } +const configs = { + recommended, + "flat/recommended": flatRecommended +}; +const rules = { + "alt-text": altText, + "anchor-has-content": anchorHasContent, + "aria-props": ariaProps, + "aria-role": ariaRole, + "aria-unsupported-elements": ariaUnsupportedElements, + "click-events-have-key-events": clickEventsHaveKeyEvents, + "form-control-has-label": formControlHasLabel, + "heading-has-content": headingHasContent, + "iframe-has-title": iframeHasTitle, + "interactive-supports-focus": interactiveSupportsFocus, + "label-has-for": labelHasFor, + "media-has-caption": mediaHasCaption, + "mouse-events-have-key-events": mouseEventsHaveKeyEvents, + "no-access-key": noAccessKey, + "no-aria-hidden-on-focusable": noAriaHiddenOnFocusable, + "no-autofocus": noAutofocus, + "no-distracting-elements": noDistractingElements, + "no-onchange": noOnchange, + "no-redundant-roles": noRedundantRoles, + "no-role-presentation-on-focusable": noRolePresentationOnFocusable, + "no-static-element-interactions": noStaticElementInteractions, + "role-has-required-aria-props": roleHasRequiredAriaProps, + "tabindex-no-positive": tabindexNoPositive }; -export = plugin; +export = { + configs, + rules +}; diff --git a/src/rules/__tests__/form-control-has-label.test.ts b/src/rules/__tests__/form-control-has-label.test.ts index 2442afd3..da466eb9 100644 --- a/src/rules/__tests__/form-control-has-label.test.ts +++ b/src/rules/__tests__/form-control-has-label.test.ts @@ -5,8 +5,22 @@ makeRuleTester("form-control-has-label", rule, { valid: [ "", "", - "", "", + ` +
+ + +
+ `, + ` +
+ +
+ +

Here is some extra info what I agree upon

+
+
+ `, `