From cf0d0e6c05bb7b17b8495500f0b3b4df15a34226 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:05:27 +1300 Subject: [PATCH 1/8] feat: restructure plugin exports to enable support for flat configs BREAKING CHANGE --- src/__tests__/index.test.js | 84 ++++++++++++++++++++++++++++++------- src/index.js | 70 +++++++++++++++++++------------ 2 files changed, 112 insertions(+), 42 deletions(-) diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index ef29c50..c1843ff 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,22 +1,76 @@ -import { generateRecommendedConfig, rules } from "../"; +import plugin from "../"; it("should have all the rules", () => { - expect(Object.keys(rules).length).toBeGreaterThan(0); + expect(Object.keys(plugin.rules).length).toBeGreaterThan(0); }); -it.each(Object.keys(rules))("%s should export required fields", (ruleName) => { - const rule = rules[ruleName]; - expect(rule).toHaveProperty("create", expect.any(Function)); - expect(rule.meta.docs.url).not.toBe(""); - expect(rule.meta.docs.category).toBe("Best Practices"); - expect(rule.meta.docs.description).not.toBe(""); +it.each(Object.entries(plugin.rules))( + "%s should export required fields", + (name, rule) => { + expect(rule).toHaveProperty("create", expect.any(Function)); + expect(rule.meta.docs.url).not.toBe(""); + expect(rule.meta.docs.category).toBe("Best Practices"); + expect(rule.meta.docs.description).not.toBe(""); + } +); + +it("has the expected recommended config", () => { + expect(plugin.configs.recommended).toMatchInlineSnapshot(` + Object { + plugins: Array [ + jest-dom, + ], + rules: Object { + jest-dom/prefer-checked: error, + jest-dom/prefer-empty: error, + jest-dom/prefer-enabled-disabled: error, + jest-dom/prefer-focus: error, + jest-dom/prefer-in-document: error, + jest-dom/prefer-required: error, + jest-dom/prefer-to-have-attribute: error, + jest-dom/prefer-to-have-class: error, + jest-dom/prefer-to-have-style: error, + jest-dom/prefer-to-have-text-content: error, + jest-dom/prefer-to-have-value: error, + }, + } + `); }); -it("should have a recommended config with recommended rules", () => { - expect( - generateRecommendedConfig({ - good: { meta: { docs: { recommended: true } } }, - bad: { meta: { docs: { recommended: false } } }, - }) - ).toEqual({ "jest-dom/good": "error" }); +it("has the expected recommended flat config", () => { + const expectJestDomPlugin = expect.objectContaining({ + meta: { + name: "eslint-plugin-example", + version: expect.any(String), + }, + }); + + expect(plugin.configs["flat/recommended"]).toMatchInlineSnapshot( + { plugins: { "jest-dom": expectJestDomPlugin } }, + ` + Object { + plugins: Object { + jest-dom: ObjectContaining { + meta: Object { + name: eslint-plugin-example, + version: Any, + }, + }, + }, + rules: Object { + jest-dom/prefer-checked: error, + jest-dom/prefer-empty: error, + jest-dom/prefer-enabled-disabled: error, + jest-dom/prefer-focus: error, + jest-dom/prefer-in-document: error, + jest-dom/prefer-required: error, + jest-dom/prefer-to-have-attribute: error, + jest-dom/prefer-to-have-class: error, + jest-dom/prefer-to-have-style: error, + jest-dom/prefer-to-have-text-content: error, + jest-dom/prefer-to-have-value: error, + }, + } + ` + ); }); diff --git a/src/index.js b/src/index.js index cbde69b..b01ff2c 100644 --- a/src/index.js +++ b/src/index.js @@ -14,33 +14,49 @@ import requireIndex from "requireindex"; //------------------------------------------------------------------------------ // import all rules in src/rules -export const rules = requireIndex(`${__dirname}/rules`); - -export const generateRecommendedConfig = (allRules) => - Object.entries(allRules).reduce( - (memo, [name, rule]) => ({ - ...memo, - ...(rule.meta.docs.recommended ? { [`jest-dom/${name}`]: "error" } : {}), - }), - {} - ); - -export const generateAllRulesConfig = (allRules) => - Object.entries(allRules).reduce( - (memo, [name]) => ({ - ...memo, - ...{ [`jest-dom/${name}`]: "error" }, - }), - {} - ); - -export const configs = { - recommended: { - plugins: ["jest-dom"], - rules: generateRecommendedConfig(rules), +const rules = requireIndex(`${__dirname}/rules`); + +const recommendedRules = Object.entries(rules).reduce( + (memo, [name, rule]) => ({ + ...memo, + ...(rule.meta.docs.recommended ? { [`jest-dom/${name}`]: "error" } : {}), + }), + {} +); + +const allRules = Object.entries(rules).reduce( + (memo, [name]) => ({ + ...memo, + ...{ [`jest-dom/${name}`]: "error" }, + }), + {} +); + +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.0.0", }, - all: { - plugins: ["jest-dom"], - rules: generateAllRulesConfig(rules), + configs: { + recommended: { + plugins: ["jest-dom"], + rules: recommendedRules, + }, + all: { + plugins: ["jest-dom"], + rules: allRules, + }, }, + rules, }; + +plugin.configs["flat/recommended"] = { + plugins: { "jest-dom": plugin }, + rules: recommendedRules, +}; +plugin.configs["flat/all"] = { + plugins: { "jest-dom": plugin }, + rules: allRules, +}; + +export default plugin; From 8af327bb9d73cab9061cd409eef4809d436bc810 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:06:47 +1300 Subject: [PATCH 2/8] test: use length check for explicit value --- src/__tests__/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index c1843ff..e30b535 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,7 +1,7 @@ import plugin from "../"; it("should have all the rules", () => { - expect(Object.keys(plugin.rules).length).toBeGreaterThan(0); + expect(Object.keys(plugin.rules)).toHaveLength(11); }); it.each(Object.entries(plugin.rules))( From bdfd02ac34de18f02fbccf9992b2c66365e046e2 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:09:59 +1300 Subject: [PATCH 3/8] fix: use correct plugin name and version in plugin meta --- src/__tests__/index.test.js | 4 ++-- src/index.js | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index e30b535..90eb60b 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -40,7 +40,7 @@ it("has the expected recommended config", () => { it("has the expected recommended flat config", () => { const expectJestDomPlugin = expect.objectContaining({ meta: { - name: "eslint-plugin-example", + name: "eslint-plugin-jest-dom", version: expect.any(String), }, }); @@ -52,7 +52,7 @@ it("has the expected recommended flat config", () => { plugins: Object { jest-dom: ObjectContaining { meta: Object { - name: eslint-plugin-example, + name: eslint-plugin-jest-dom, version: Any, }, }, diff --git a/src/index.js b/src/index.js index b01ff2c..0c7f03e 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,10 @@ //------------------------------------------------------------------------------ import requireIndex from "requireindex"; +import { + name as packageName, + version as packageVersion, +} from "../package.json"; //------------------------------------------------------------------------------ // Plugin Definition @@ -34,8 +38,8 @@ const allRules = Object.entries(rules).reduce( const plugin = { meta: { - name: "eslint-plugin-example", - version: "1.0.0", + name: packageName, + version: packageVersion, }, configs: { recommended: { From 2091bd23b7f7caa084d210f5d2fa6144f1150d0c Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:27:14 +1300 Subject: [PATCH 4/8] fix: export `configs` and `rules` to ensure CJS and `.eslintrc` compatability --- src/__tests__/index.test.js | 10 +++++----- src/index.js | 9 +++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index 90eb60b..879498f 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,10 +1,10 @@ -import plugin from "../"; +import { configs, rules } from "../"; it("should have all the rules", () => { - expect(Object.keys(plugin.rules)).toHaveLength(11); + expect(Object.keys(rules)).toHaveLength(11); }); -it.each(Object.entries(plugin.rules))( +it.each(Object.entries(rules))( "%s should export required fields", (name, rule) => { expect(rule).toHaveProperty("create", expect.any(Function)); @@ -15,7 +15,7 @@ it.each(Object.entries(plugin.rules))( ); it("has the expected recommended config", () => { - expect(plugin.configs.recommended).toMatchInlineSnapshot(` + expect(configs.recommended).toMatchInlineSnapshot(` Object { plugins: Array [ jest-dom, @@ -45,7 +45,7 @@ it("has the expected recommended flat config", () => { }, }); - expect(plugin.configs["flat/recommended"]).toMatchInlineSnapshot( + expect(configs["flat/recommended"]).toMatchInlineSnapshot( { plugins: { "jest-dom": expectJestDomPlugin } }, ` Object { diff --git a/src/index.js b/src/index.js index 0c7f03e..e1d1920 100644 --- a/src/index.js +++ b/src/index.js @@ -17,8 +17,8 @@ import { // Plugin Definition //------------------------------------------------------------------------------ -// import all rules in src/rules -const rules = requireIndex(`${__dirname}/rules`); +// import all rules in src/rules and re-export them for .eslintrc configs +export const rules = requireIndex(`${__dirname}/rules`); const recommendedRules = Object.entries(rules).reduce( (memo, [name, rule]) => ({ @@ -64,3 +64,8 @@ plugin.configs["flat/all"] = { }; export default plugin; + +// explicitly export config to allow using this plugin in CJS-based +// eslint.config.js files without needing to deal with the .default +// and also retain backwards compatibility with `.eslintrc` configs +export const configs = plugin.configs; From 85efb6d223367f9b240ab83ac1c34389af49f8ad Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:27:36 +1300 Subject: [PATCH 5/8] test: ensure that the `plugin` has the same `configs` and `rules` --- src/__tests__/index.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index 879498f..af8e8d9 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,4 +1,9 @@ -import { configs, rules } from "../"; +import plugin, { configs, rules } from "../"; + +it("includes the configs and rules on the plugin", () => { + expect(plugin).toHaveProperty("configs", configs); + expect(plugin).toHaveProperty("rules", rules); +}); it("should have all the rules", () => { expect(Object.keys(rules)).toHaveLength(11); From dc4010db22a19bdf8409b56f1ed435dc085ccaba Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:31:21 +1300 Subject: [PATCH 6/8] docs: include notes about `eslint.config.js` support --- README.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd77405..7abd300 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,17 @@ This library has a required `peerDependencies` listing for [`ESLint`](https://es ## Usage +> [!NOTE] +> +> `eslint.config.js` is supported, though most of the plugin documentation still +> currently uses `.eslintrc` syntax; compatible versions of configs are available +> prefixed with `flat/` and may be subject to small breaking changes while ESLint +> v9 is being finalized. +> +> Refer to the +> [ESLint documentation on the new configuration file format](https://eslint.org/docs/latest/use/configure/configuration-files-new) +> for more. + Add `jest-dom` to the plugins section of your `.eslintrc.js` configuration file. You can omit the `eslint-plugin-` prefix: @@ -78,8 +89,7 @@ This plugin exports a recommended configuration that enforces good `jest-dom` practices _(you can find more info about enabled rules in [Supported Rules section](#supported-rules))_. -To enable this configuration use the `extends` property in your `.eslintrc.js` -config file: +To enable this configuration with `.eslintrc`, use the `extends` property: ```javascript module.exports = { @@ -90,6 +100,20 @@ module.exports = { }; ``` +To enable this configuration with `eslint.config.js`, use +`jestDom.configs['flat/recommended']`: + +```javascript +module.exports = [ + { + files: [ + /* glob matching your test files */ + ], + ...require("eslint-plugin-jest-dom").configs["flat/recommended"], + }, +]; +``` + ## Supported Rules From 3097266bbb42625a967473267ab9fe76e0b08324 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 08:34:18 +1300 Subject: [PATCH 7/8] chore: exclude flat configs from generated rules table --- .eslint-doc-generatorrc.js | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .eslint-doc-generatorrc.js diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js new file mode 100644 index 0000000..d236862 --- /dev/null +++ b/.eslint-doc-generatorrc.js @@ -0,0 +1,10 @@ +/** @type {import('eslint-doc-generator').GenerateOptions} */ +const config = { + ignoreConfig: [ + 'all', + 'flat/all', + 'flat/recommended', + ], +}; + +module.exports = config; diff --git a/package.json b/package.json index 1a1cb46..4e81bf8 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "scripts": { "build": "kcd-scripts build", "pregenerate-readme-table": "npm run build", - "generate-readme-table": "eslint-doc-generator --ignore-config all", + "generate-readme-table": "eslint-doc-generator", "lint": "kcd-scripts lint", "lint:generate-readme-table": "npm run generate-readme-table -- --check", "setup": "npm install && npm run validate -s", From 7fc2f99e2ff18e203ff06e78d73772d14a00c5bc Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Feb 2024 09:33:09 +1300 Subject: [PATCH 8/8] refactor: we're recommending all rules right now --- src/index.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/index.js b/src/index.js index e1d1920..0741088 100644 --- a/src/index.js +++ b/src/index.js @@ -20,14 +20,6 @@ import { // import all rules in src/rules and re-export them for .eslintrc configs export const rules = requireIndex(`${__dirname}/rules`); -const recommendedRules = Object.entries(rules).reduce( - (memo, [name, rule]) => ({ - ...memo, - ...(rule.meta.docs.recommended ? { [`jest-dom/${name}`]: "error" } : {}), - }), - {} -); - const allRules = Object.entries(rules).reduce( (memo, [name]) => ({ ...memo, @@ -36,6 +28,8 @@ const allRules = Object.entries(rules).reduce( {} ); +const recommendedRules = allRules; + const plugin = { meta: { name: packageName,