Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support flat config and eslint v9 #1073

Merged
merged 13 commits into from
Apr 18, 2024
Merged
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__fixtures__/
dist/
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jobs:
- name: Lint and test
run: |
yarn install --frozen-lockfile
yarn prepublishOnly
yarn build
yarn lint
yarn prettier --check '**/*'
yarn test
yarn test:integration
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/.husky/
/.idea/
/dist/
/node_modules/
node_modules
/docs/.vitepress/dist
/docs/.vitepress/cache
yarn-error.log
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
.eslintcache
.eslintignore
.gitignore
.husky
.npmignore
.prettierignore
LICENSE
yarn.lock
dist
__fixtures__
1 change: 1 addition & 0 deletions __tests__/integrations/__fixtures__/flat-config/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
11 changes: 11 additions & 0 deletions __tests__/integrations/__fixtures__/flat-config/a.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<div>
<img src="foo" />
</div>
</template>

<script>
import { ref } from "vue";
const counter = ref(0);
console.log("counter", counter);
</script>
10 changes: 10 additions & 0 deletions __tests__/integrations/__fixtures__/flat-config/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import plugin from "eslint-plugin-vuejs-accessibility";

export default [
...plugin.configs["flat/recommended"],
{
rules: {
"vuejs-accessibility/alt-text": "warn"
}
}
];
11 changes: 11 additions & 0 deletions __tests__/integrations/__fixtures__/flat-config/package.json
Original file line number Diff line number Diff line change
@@ -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:../../../.."
}
}
13 changes: 13 additions & 0 deletions __tests__/integrations/__fixtures__/legacy-config/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
1 change: 1 addition & 0 deletions __tests__/integrations/__fixtures__/legacy-config/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
11 changes: 11 additions & 0 deletions __tests__/integrations/__fixtures__/legacy-config/a.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<div>
<img src="foo" />
</div>
</template>

<script>
import { ref } from "vue";
const counter = ref(0);
console.log("counter", counter);
</script>
11 changes: 11 additions & 0 deletions __tests__/integrations/__fixtures__/legacy-config/package.json
Original file line number Diff line number Diff line change
@@ -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:../../../.."
}
}
40 changes: 40 additions & 0 deletions __tests__/integrations/flat-config.spec.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
8 changes: 8 additions & 0 deletions __tests__/integrations/helper.ts
Original file line number Diff line number Diff line change
@@ -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")
);
}
40 changes: 40 additions & 0 deletions __tests__/integrations/legacy-config.spec.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
File renamed without changes.
28 changes: 27 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -23,8 +23,8 @@ export default defineLoader({
});

function getRecommendedRules() {
if (recommended.rules) {
return Object.keys(recommended.rules).map(removeRulePrefix);
if (baseRules) {
return Object.keys(baseRules).map(removeRulePrefix);
}
return [];
}
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
"version": "2.2.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"
Expand Down Expand Up @@ -42,15 +47,18 @@
"@types/eslint-scope": "^3.7.2",
"@types/jest": "^29.2.5",
"@types/node": "^20.1.0",
"@types/semver": "^7.5.7",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^6.9.1",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.8.0",
"eslint-plugin-eslint-plugin": "^6.0.0",
"globals": "^14.0.0",
"husky": "^9.0.5",
"jest": "^29.2.2",
"np": "^10.0.0",
"prettier": "^3.0.0",
"pretty-quick": "^4.0.0",
"semver": "^7.6.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.3.0",
"typescript": "^5.0.2",
Expand Down
34 changes: 34 additions & 0 deletions src/configs/flat/recommended.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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
}
];

export = recommended;
24 changes: 2 additions & 22 deletions src/configs/recommended.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Linter } from "eslint";
import { rules } from "./rules";

const recommended: Linter.BaseConfig = {
parser: require.resolve("vue-eslint-parser"),
Expand All @@ -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
};

export default recommended;
26 changes: 26 additions & 0 deletions src/configs/rules.ts
Original file line number Diff line number Diff line change
@@ -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 };
Loading