Skip to content

Commit 3edcbc0

Browse files
kazuponvhoyer
andauthored
feat: support flat config and eslint v9 (#1073)
* feat: support flat config * refactor: tweak test context * fix: update docs & tweak vitepress * fix: remove * fix: add name for eslint inspector debugging * test: eslint v9 testing * fix: support eslint v8.x and v9 compatibility * 🙈 Add .eslintignore file to skip test fixtures * 🚚 Move fixtures to a separate __fixtures__ folder * 🚨 Fix prettier warnings * 🐛 Prettier broke @ts-expect-error --------- Co-authored-by: Vinícius Hoyer <contact@vhoyer.dev>
1 parent d9a8cb7 commit 3edcbc0

28 files changed

+373
-104
lines changed

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__fixtures__/
2+
dist/

.github/workflows/main.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ jobs:
1919
- name: Lint and test
2020
run: |
2121
yarn install --frozen-lockfile
22-
yarn prepublishOnly
22+
yarn build
2323
yarn lint
2424
yarn prettier --check '**/*'
2525
yarn test
26+
yarn test:integration

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/.husky/
33
/.idea/
44
/dist/
5-
/node_modules/
5+
node_modules
66
/docs/.vitepress/dist
77
/docs/.vitepress/cache
88
yarn-error.log

.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
.eslintcache
2+
.eslintignore
23
.gitignore
34
.husky
45
.npmignore
56
.prettierignore
67
LICENSE
78
yarn.lock
89
dist
10+
__fixtures__
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<template>
2+
<div>
3+
<img src="foo" />
4+
</div>
5+
</template>
6+
7+
<script>
8+
import { ref } from "vue";
9+
const counter = ref(0);
10+
console.log("counter", counter);
11+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import plugin from "eslint-plugin-vuejs-accessibility";
2+
3+
export default [
4+
...plugin.configs["flat/recommended"],
5+
{
6+
rules: {
7+
"vuejs-accessibility/alt-text": "warn"
8+
}
9+
}
10+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"private": true,
3+
"name": "integration-test-for-flat-config",
4+
"type": "module",
5+
"version": "1.0.0",
6+
"description": "Integration test for flat config",
7+
"dependencies": {
8+
"eslint": "^9.0.0",
9+
"eslint-plugin-vuejs-accessibility": "file:../../../.."
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"root": true,
3+
"extends": ["plugin:vuejs-accessibility/recommended"],
4+
"plugins": ["vuejs-accessibility"],
5+
"parser": "vue-eslint-parser",
6+
"parserOptions": {
7+
"sourceType": "module",
8+
"ecmaVersion": 2019
9+
},
10+
"rules": {
11+
"vuejs-accessibility/alt-text": "warn"
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<template>
2+
<div>
3+
<img src="foo" />
4+
</div>
5+
</template>
6+
7+
<script>
8+
import { ref } from "vue";
9+
const counter = ref(0);
10+
console.log("counter", counter);
11+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"private": true,
3+
"name": "integration-test-for-legacy-config",
4+
"type": "module",
5+
"version": "1.0.0",
6+
"description": "Integration test for legacy config",
7+
"dependencies": {
8+
"eslint": "^8.57.0-0",
9+
"eslint-plugin-vuejs-accessibility": "file:../../../.."
10+
}
11+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import cp from "child_process";
2+
import path from "path";
3+
import semver from "semver";
4+
import { readPackageJson } from "./helper";
5+
6+
const ESLINT = `.${path.sep}node_modules${path.sep}.bin${path.sep}eslint`;
7+
8+
describe("Integration with flat config", () => {
9+
let originalCwd: null | string = null;
10+
const dirFixture = path.join(__dirname, "__fixtures__/flat-config");
11+
12+
beforeEach(() => {
13+
originalCwd = process.cwd();
14+
process.chdir(dirFixture);
15+
cp.execSync("npm i -f", { stdio: "inherit" });
16+
});
17+
afterEach(() => {
18+
originalCwd && process.chdir(originalCwd);
19+
});
20+
21+
it("should work with config", () => {
22+
expect.assertions(2);
23+
24+
const eslintPackageJson = readPackageJson(
25+
path.resolve(dirFixture, "node_modules/eslint")
26+
);
27+
28+
if (!semver.satisfies(process.version, eslintPackageJson.engines.node)) {
29+
return;
30+
}
31+
32+
const result = JSON.parse(
33+
cp.execSync(`${ESLINT} a.vue --max-warnings 1 --format=json`, {
34+
encoding: "utf-8"
35+
})
36+
);
37+
expect(result.length).toBe(1);
38+
expect(result[0].messages[0].messageId).toBe("imgMissingAlt");
39+
});
40+
});

__tests__/integrations/helper.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import fs from "fs";
2+
import path from "path";
3+
4+
export function readPackageJson(base: string) {
5+
return JSON.parse(
6+
fs.readFileSync(path.resolve(base, "package.json"), "utf-8")
7+
);
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import cp from "child_process";
2+
import path from "path";
3+
import semver from "semver";
4+
import { readPackageJson } from "./helper";
5+
6+
const ESLINT = `.${path.sep}node_modules${path.sep}.bin${path.sep}eslint`;
7+
8+
describe("Integration with legacy config", () => {
9+
let originalCwd: null | string = null;
10+
const dirFixture = path.join(__dirname, "__fixtures__/legacy-config");
11+
12+
beforeEach(() => {
13+
originalCwd = process.cwd();
14+
process.chdir(dirFixture);
15+
cp.execSync("npm i -f", { stdio: "inherit" });
16+
});
17+
afterEach(() => {
18+
originalCwd && process.chdir(originalCwd);
19+
});
20+
21+
it("should work with config", () => {
22+
expect.assertions(2);
23+
24+
const eslintPackageJson = readPackageJson(
25+
path.resolve(dirFixture, "node_modules/eslint")
26+
);
27+
28+
if (!semver.satisfies(process.version, eslintPackageJson.engines.node)) {
29+
return;
30+
}
31+
32+
const result = JSON.parse(
33+
cp.execSync(`${ESLINT} a.vue --max-warnings 1 --format=json`, {
34+
encoding: "utf-8"
35+
})
36+
);
37+
expect(result.length).toBe(1);
38+
expect(result[0].messages[0].messageId).toBe("imgMissingAlt");
39+
});
40+
});
File renamed without changes.

docs/index.md

+27-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,33 @@ pnpm add -D eslint-plugin-vuejs-accessibility
2222

2323
## 📖 Usage
2424

25-
Add `vuejs-accessibility` to the plugins section of your `eslint` configuration. You can omit the `eslint-plugin-` prefix:
25+
### Configuration (`eslint.config.js`)
26+
27+
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.
28+
29+
Example eslint.config.js:
30+
31+
```js
32+
import pluginVueA11y from "eslint-plugin-vuejs-accessibility";
33+
34+
export default [
35+
// add more generic rulesets here, such as:
36+
// js.configs.recommended,
37+
...pluginVueA11y.configs["flat/recommended"],
38+
{
39+
rules: {
40+
// override/add rules settings here, such as:
41+
// "vuejs-accessibility/alt-text": "error"
42+
}
43+
}
44+
];
45+
```
46+
47+
### Configuration (`.eslintrc`)
48+
49+
Use `.eslintrc.*` file to configure rules in ESLint < v9. See also: https://eslint.org/docs/latest/use/configure/.
50+
51+
Add `vuejs-accessibility` to the plugins section of your configuration. You can omit the `eslint-plugin-` prefix:
2652

2753
```json
2854
{

docs/rule-overview/rule-overview.data.ts docs/rule-overview/rule-overview.data.mts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { defineLoader } from "vitepress";
2-
import recommended from "../../src/configs/recommended";
3-
import { rules } from "../.vitepress/rulesForSidebar";
2+
import { rules as baseRules } from "../../src/configs/rules.js";
3+
import { rules } from "../.vitepress/rulesForSidebar.js";
44

55
export type Data = Array<{
66
name: string;
@@ -23,8 +23,8 @@ export default defineLoader({
2323
});
2424

2525
function getRecommendedRules() {
26-
if (recommended.rules) {
27-
return Object.keys(recommended.rules).map(removeRulePrefix);
26+
if (baseRules) {
27+
return Object.keys(baseRules).map(removeRulePrefix);
2828
}
2929
return [];
3030
}

package.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
"version": "2.2.1",
44
"description": "An eslint plugin for checking Vue.js files for accessibility",
55
"main": "dist/index.js",
6+
"files": [
7+
"dist"
8+
],
69
"scripts": {
710
"lint": "eslint --cache .",
11+
"build": "tsc -p tsconfig.build.json",
812
"prepublishOnly": "tsc -p tsconfig.build.json",
913
"test": "jest",
14+
"test:integration": "jest --testTimeout 60000 --testRegex \".*\\.spec\\.ts$\"",
1015
"release": "np",
1116
"docs:dev": "vitepress dev docs",
1217
"docs:build": "vitepress build docs"
@@ -42,15 +47,18 @@
4247
"@types/eslint-scope": "^3.7.2",
4348
"@types/jest": "^29.2.5",
4449
"@types/node": "^20.1.0",
50+
"@types/semver": "^7.5.7",
4551
"@typescript-eslint/eslint-plugin": "^7.0.0",
46-
"@typescript-eslint/parser": "^6.9.1",
52+
"@typescript-eslint/parser": "^7.0.0",
4753
"eslint": "^8.8.0",
4854
"eslint-plugin-eslint-plugin": "^6.0.0",
55+
"globals": "^14.0.0",
4956
"husky": "^9.0.5",
5057
"jest": "^29.2.2",
5158
"np": "^10.0.0",
5259
"prettier": "^3.0.0",
5360
"pretty-quick": "^4.0.0",
61+
"semver": "^7.6.0",
5462
"ts-jest": "^29.0.3",
5563
"ts-node": "^10.3.0",
5664
"typescript": "^5.0.2",

src/configs/flat/recommended.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import globals from "globals";
2+
import { rules } from "../rules";
3+
4+
const recommended = [
5+
{
6+
name: "vuejs-accessibility:setup:base",
7+
plugins: {
8+
get "vuejs-accessibility"() {
9+
return require("../../index");
10+
}
11+
},
12+
languageOptions: {
13+
sourceType: "module",
14+
globals: globals.browser
15+
}
16+
},
17+
{
18+
name: "vuejs-accessibility:setup:with-files-rules-and-parser",
19+
files: ["*.vue", "**/*.vue"],
20+
plugins: {
21+
get "vuejs-accessibility"() {
22+
return require("../../index");
23+
}
24+
},
25+
languageOptions: {
26+
parser: require("vue-eslint-parser"),
27+
sourceType: "module",
28+
globals: globals.browser
29+
},
30+
rules
31+
}
32+
];
33+
34+
export = recommended;

src/configs/recommended.ts

+2-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Linter } from "eslint";
2+
import { rules } from "./rules";
23

34
const recommended: Linter.BaseConfig = {
45
parser: require.resolve("vue-eslint-parser"),
@@ -11,28 +12,7 @@ const recommended: Linter.BaseConfig = {
1112
es6: true
1213
},
1314
plugins: ["vuejs-accessibility"],
14-
rules: {
15-
"vuejs-accessibility/alt-text": "error",
16-
"vuejs-accessibility/anchor-has-content": "error",
17-
"vuejs-accessibility/aria-props": "error",
18-
"vuejs-accessibility/aria-role": "error",
19-
"vuejs-accessibility/aria-unsupported-elements": "error",
20-
"vuejs-accessibility/click-events-have-key-events": "error",
21-
"vuejs-accessibility/form-control-has-label": "error",
22-
"vuejs-accessibility/heading-has-content": "error",
23-
"vuejs-accessibility/iframe-has-title": "error",
24-
"vuejs-accessibility/interactive-supports-focus": "error",
25-
"vuejs-accessibility/label-has-for": "error",
26-
"vuejs-accessibility/media-has-caption": "error",
27-
"vuejs-accessibility/mouse-events-have-key-events": "error",
28-
"vuejs-accessibility/no-access-key": "error",
29-
"vuejs-accessibility/no-autofocus": "error",
30-
"vuejs-accessibility/no-distracting-elements": "error",
31-
"vuejs-accessibility/no-redundant-roles": "error",
32-
"vuejs-accessibility/no-static-element-interactions": "error",
33-
"vuejs-accessibility/role-has-required-aria-props": "error",
34-
"vuejs-accessibility/tabindex-no-positive": "error"
35-
}
15+
rules
3616
};
3717

3818
export default recommended;

src/configs/rules.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { Linter } from "eslint";
2+
3+
const rules = {
4+
"vuejs-accessibility/alt-text": "error",
5+
"vuejs-accessibility/anchor-has-content": "error",
6+
"vuejs-accessibility/aria-props": "error",
7+
"vuejs-accessibility/aria-role": "error",
8+
"vuejs-accessibility/aria-unsupported-elements": "error",
9+
"vuejs-accessibility/click-events-have-key-events": "error",
10+
"vuejs-accessibility/form-control-has-label": "error",
11+
"vuejs-accessibility/heading-has-content": "error",
12+
"vuejs-accessibility/iframe-has-title": "error",
13+
"vuejs-accessibility/interactive-supports-focus": "error",
14+
"vuejs-accessibility/label-has-for": "error",
15+
"vuejs-accessibility/media-has-caption": "error",
16+
"vuejs-accessibility/mouse-events-have-key-events": "error",
17+
"vuejs-accessibility/no-access-key": "error",
18+
"vuejs-accessibility/no-autofocus": "error",
19+
"vuejs-accessibility/no-distracting-elements": "error",
20+
"vuejs-accessibility/no-redundant-roles": "error",
21+
"vuejs-accessibility/no-static-element-interactions": "error",
22+
"vuejs-accessibility/role-has-required-aria-props": "error",
23+
"vuejs-accessibility/tabindex-no-positive": "error"
24+
} satisfies Linter.RulesRecord;
25+
26+
export { rules };

0 commit comments

Comments
 (0)