Repository: cypress-io/eslint-plugin-cypress (js)

Issue:
Upgrade to new ESLint Flat configuration files
Hey there, thanks for your ESLint plugin 🙌. Starting with v9, ESLint provides a [new flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) (`eslint.config.js`). Also see the [configuration migration guide](https://eslint.org/docs/latest/use/configure/migration-guide) and the [plugin migration guide](https://eslint.org/docs/latest/extend/plugin-migration-flat-config).

Do you have any plans to upgrade this plugin to the new format? For example, the `env` field does not exist anymore and globals have to provided in a different way. Also see my [Stack Overflow answer here](https://stackoverflow.com/a/77759885/9655481). And there are many other breaking changes too.

I've just migrated my [`eslint-plugin-erb`](https://github.com/Splines/eslint-plugin-erb/tree/v2.0.0) to the new API. Feel free to take inspiration from it, e.g. I provide a legacy recommended configuration for backwards compatibility [here](https://github.com/Splines/eslint-plugin-erb/blob/v2.0.0/lib/index.js#L22-L36).

Pull request description:
feat: add flat configurations
- closes #146

## Issue

[eslint-plugin-cypress@3.2.0](https://github.com/cypress-io/eslint-plugin-cypress/releases/tag/v3.2.0) added support for the use of [Flat config files](https://eslint.org/docs/latest/use/configure/configuration-files) with ESLint `8.57.0` and above through the compatibility utility [@eslint/eslintrc](https://www.npmjs.com/package/@eslint/eslintrc).

To fully support [Flat config files](https://eslint.org/docs/latest/use/configure/configuration-files) the plugin needs to provide corresponding configurations which do not rely on any compatibility utility.

## Goals

- Offer configurations directly usable in the ESLint v9 [Configuration Files](https://eslint.org/docs/latest/use/configure/configuration-files) default flat config format (`eslint.config.*.js`). The new configurations should be based on the existing `.eslintrc.*` configurations.
- Remove the dependency on the [@eslint/eslintrc](https://www.npmjs.com/package/@eslint/eslintrc) utility, also referred to as the [Backwards compatibility utility](https://eslint.org/blog/2022/08/new-config-system-part-2/#backwards-compatibility-utility)
- Continue to offer configurations for the [Configuration Files (Deprecated)](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated) format (`.eslintrc.*`)
- Prepare for removal of deprecated configurations with minimal impact for users and for plugin maintenance

## Changes

- The set of flat configs is made available through `eslint-plugin-cypress/flat`:

| Configuration         | Content                                                                                                                                                                                                                                                                                                                                                                                                                     |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configs.globals`     | defines globals `cy`, `Cypress`, `expect`, `assert` and `chai` used in Cypress test specs as well as `globals.browser` and `globals.mocha` from [globals](https://www.npmjs.com/package/globals). Additionally, `languageOptions` of `ecmaVersion: 2019` and `sourceType: 'module'` for backwards compatibility with earlier versions of this plugin are defined. There are no default rules enabled in this configuration. |
| `configs.recommended` | enables [recommended Rules](README.md#rules). It includes also `configs.global` (see above)                                                                                                                                                                                                                                                                                                                                 |

- Documentation is updated accordingly


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2bbdab4..2d04081 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -34,7 +34,7 @@ To add a new rule:
 * Run `npm run lint`
 * Run `npm test` to run [Jest](https://jestjs.io/) (or run `npm start` to run [Jest](https://jestjs.io/) in [watchAll](https://jestjs.io/docs/cli#--watchall) mode where it remains active and reruns when source changes are made)
 * Make sure all tests are passing
-* Add the rule to [index.js](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/index.js)
+* Add the rule to [legacy.js](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/legacy.js) and to [flat.js](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/lib/flat.js)
 * Create a git commit with a commit message similar to: `feat: add rule <description>` (see [commit message conventions](https://github.com/semantic-release/semantic-release#commit-message-format))
 * Create a PR from your branch
 
diff --git a/FLAT-CONFIG.md b/FLAT-CONFIG.md
index b797ce1..5a7b286 100644
--- a/FLAT-CONFIG.md
+++ b/FLAT-CONFIG.md
@@ -10,7 +10,7 @@ Usage with ESLint `8.57.0` and ESLint `9.x` is described.
 
 Previously, ESLint had announced in their blog post [Flat config rollout plans](https://eslint.org/blog/2023/10/flat-config-rollout-plans/) in October 2023 that flat config was planned to be the default in ESLint `v9.0.0` and that the eslintrc configuration system is planned to be removed in the future ESLint `v10.0.0`.
 
-The Cypress ESLint Plugin (`eslint-plugin-cypress`) is currently based on ESLint `8.x` and has not yet been migrated to support Flat Config natively. [ESLint's new config system, Part 2: Introduction to flat config](https://eslint.org/blog/2022/08/new-config-system-part-2/) from August 2022 introduced the [Backwards compatibility utility](https://eslint.org/blog/2022/08/new-config-system-part-2/#backwards-compatibility-utility). This gives the capability to use `eslint-plugin-cypress` in an ESLint flat config environment.
+Cypress ESLint Plugin (`eslint-plugin-cypress`) in release [3.2.0](https://github.com/cypress-io/eslint-plugin-cypress/releases/tag/v3.2.0) offered the first support of ESLint `9.x` flat config files using the [Backwards compatibility utility](https://eslint.org/blog/2022/08/new-config-system-part-2/#backwards-compatibility-utility). Current releases have removed the dependency on this utility and the examples in this document have been updated correspondingly.
 
 The following information details installation and usage examples for `eslint-plugin-cypress` together with related plugins in an ESLint flat config environment.
 
@@ -19,60 +19,82 @@ The following information details installation and usage examples for `eslint-pl
 It is recommended to use a minimum ESLint `8.x` version [eslint@8.57.0](https://github.com/eslint/eslint/releases/tag/v8.57.0) or ESLint `9.x`.
 
 ```shell
-npm install eslint @eslint/eslintrc eslint-plugin-cypress@^3.1.1 --save-dev
+npm install eslint eslint-plugin-cypress --save-dev
 ```
 
 or
 
 ```shell
-yarn add eslint @eslint/eslintrc eslint-plugin-cypress@^3.1.1 --dev
+yarn add eslint eslint-plugin-cypress --dev
 ```
 
 ## Usage examples
 
-Add a flat configuration file `eslint.config.mjs` file to the root directory of your Cypress project. In the following sections, different examples of possible configuration file contents are given, together with some brief explanations. Adapt these examples according to your needs.
+Add a flat configuration file `eslint.config.mjs` to the root directory of your Cypress project and include the following instructions to import the available flat configurations using:
+
+```shell
+import pluginCypress from 'eslint-plugin-cypress/flat'
+```
+
+There are two specific flat configurations available:
+
+| Configuration         | Content                                                                                                                                                                                                                                                                                                                                                                                                                     |
+| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `configs.globals`     | defines globals `cy`, `Cypress`, `expect`, `assert` and `chai` used in Cypress test specs as well as `globals.browser` and `globals.mocha` from [globals](https://www.npmjs.com/package/globals). Additionally, `languageOptions` of `ecmaVersion: 2019` and `sourceType: 'module'` for backwards compatibility with earlier versions of this plugin are defined. There are no default rules enabled in this configuration. |
+| `configs.recommended` | enables [recommended Rules](README.md#rules). It includes also `configs.global` (see above)                                                                                                                                                                                                                                                                                                                                 |
+
+In the following sections, different examples of possible configuration file contents are given, together with some brief explanations. Adapt these examples according to your needs.
 
 ### Cypress
 
-All rules from `eslint-plugin-cypress` are available through the `FlatCompat` class of [@eslint/eslintrc](https://www.npmjs.com/package/@eslint/eslintrc).
+All rules from `eslint-plugin-cypress` are available through `eslint-plugin-cypress/flat`.
 - [cypress/unsafe-to-chain-command](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/unsafe-to-chain-command.md) is active and set to `error`
 
 ```js
-import { FlatCompat } from '@eslint/eslintrc'
-const compat = new FlatCompat()
+import pluginCypress from 'eslint-plugin-cypress/flat'
 export default [
-  ...compat.config(
-    {
-      plugins: ['cypress'],
-      rules: {
-        'cypress/unsafe-to-chain-command': 'error'
-      }
-    })
+  {
+    plugins: {
+      cypress: pluginCypress
+    },
+    rules: {
+      'cypress/unsafe-to-chain-command': 'error'
+    }
+  }
 ]
 ```
 
 ### Cypress recommended
 
-The `eslint-plugin-cypress` [recommended rules](README.md#rules) `plugin:cypress/recommended` are activated, except for
+The `eslint-plugin-cypress` [recommended rules](README.md#rules) `configs.recommended` are activated, except for
 - [cypress/no-unnecessary-waiting](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-unnecessary-waiting.md) set to `off`
 
 ```js
-import { FlatCompat } from '@eslint/eslintrc'
-const compat = new FlatCompat()
+import pluginCypress from 'eslint-plugin-cypress/flat'
 export default [
-  ...compat.config(
-    {
-      extends: ['plugin:cypress/recommended'],
-      rules: {
-        'cypress/no-unnecessary-waiting': 'off'
-      }
-    })
+  pluginCypress.configs.recommended,
+  {
+    rules: {
+      'cypress/no-unnecessary-waiting': 'off'
+    }
+  }
+]
+```
+
+### Cypress globals
+
+The `configs.globals` are activated.
+
+```js
+import pluginCypress from 'eslint-plugin-cypress/flat'
+export default [
+  pluginCypress.configs.globals
 ]
 ```
 
 ### Cypress and Mocha recommended
 
-[eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) is added to the previous example. This plugin offers a flat file recommended option `configs.flat.recommended`.
+[eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) is added to the example [Cypress recommended](#cypress-recommended). This plugin offers a flat file recommended option `configs.flat.recommended`.
 
 The settings for individual `mocha` rules from the `configs.flat.recommended` option are changed.
 - [mocha/no-exclusive-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-exclusive-tests.md) and [mocha/no-skipped-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-skipped-tests.md) are set to `error` instead of `warn`
@@ -83,52 +105,42 @@ npm install eslint-plugin-mocha@^10.4.3 --save-dev
 ```
 
 ```js
-import { FlatCompat } from '@eslint/eslintrc'
-import mochaPlugin from 'eslint-plugin-mocha'
-const compat = new FlatCompat()
+import pluginMocha from 'eslint-plugin-mocha'
+import pluginCypress from 'eslint-plugin-cypress/flat'
 export default [
-  mochaPlugin.configs.flat.recommended, {
+  pluginMocha.configs.flat.recommended,
+  pluginCypress.configs.recommended,
+  {
     rules: {
-      'mocha/no-exclusive-tests': 'error',
-      'mocha/no-skipped-tests': 'error',
-      'mocha/no-mocha-arrows': 'off'
+      'mocha/no-exclusive-tests': 'warn',
+      'mocha/no-skipped-tests': 'warn',
+      'mocha/no-mocha-arrows': 'off',
+      'cypress/no-unnecessary-waiting': 'off'
     }
-  },
-  ...compat.config(
-    {
-      extends: ['plugin:cypress/recommended'],
-      rules: {
-        'cypress/no-unnecessary-waiting': 'off'
-      }
-    })
+  }
 ]
 ```
 
 ### Cypress and Chai recommended
 
-[eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) is combined with the Cypress plugin `eslint-plugin-cypress`.
+[eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) (minimum version [eslint-plugin-chai-friendly@0.8.0](https://github.com/ihordiachenko/eslint-plugin-chai-friendly/releases/tag/v0.8.0) required for ESLint v9 support and flat config support) is combined with the Cypress plugin `eslint-plugin-cypress`.
 
-The [eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) plugin does not currently offer flat config options and so the `FlatCompat` class of [@eslint/eslintrc](https://www.npmjs.com/package/@eslint/eslintrc) enables this plugin to be used too. The recommended rules for both plugins are used: `plugin:cypress/recommended` and `plugin:chai-friendly/recommended`.
+The recommended rules for both plugins are used: `pluginCypress.configs.recommended` and `pluginChaiFriendly.configs.recommended`:
 
 ```shell
-npm install eslint-plugin-chai-friendly --save-dev
+npm install eslint-plugin-chai-friendly@^0.8.0 --save-dev
 ```
 
 ```js
-import { FlatCompat } from '@eslint/eslintrc'
-const compat = new FlatCompat()
+import pluginCypress from 'eslint-plugin-cypress/flat'
+import pluginChaiFriendly from 'eslint-plugin-chai-friendly'
 export default [
-  ...compat.config(
-    {
-      extends: [
-        'plugin:cypress/recommended',
-        'plugin:chai-friendly/recommended'
-      ],
-      rules: {
-        'cypress/no-unnecessary-waiting': 'off',
-      }
-    })
+  pluginCypress.configs.recommended,
+  pluginChaiFriendly.configs.recommended,
+  {
+    rules: {
+      'cypress/no-unnecessary-waiting': 'off',
+    },
+  }
 ]
 ```
-
-**Pending the resolution of issue https://github.com/ihordiachenko/eslint-plugin-chai-friendly/issues/32 [eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) cannot be used with ESLint `v9`.**
diff --git a/README.md b/README.md
index b466bc2..c43400c 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Note: If you installed ESLint globally then you must also install `eslint-plugin
 ## Installation
 
 Prerequisites: [ESLint](https://www.npmjs.com/package/eslint) `v7`, `v8` or `v9`.
-This plugin supports the use of [Flat config files](https://eslint.org/docs/latest/use/configure/configuration-files) with ESLint `8.57.0` and above through [@eslint/eslintrc](https://www.npmjs.com/package/@eslint/eslintrc).
+This plugin supports the use of [Flat config files](https://eslint.org/docs/latest/use/configure/configuration-files) with ESLint `8.57.0` and above.
 
 ```sh
 npm install eslint-plugin-cypress --save-dev
diff --git a/index.js b/legacy.js
similarity index 100%
rename from index.js
rename to legacy.js
diff --git a/lib/flat.js b/lib/flat.js
new file mode 100644
index 0000000..b62dce3
--- /dev/null
+++ b/lib/flat.js
@@ -0,0 +1,66 @@
+const globals = require('globals')
+const { name, version } = require('../package.json')
+
+const plugin = {
+  meta: { name, version },
+  configs: {},
+  rules: {
+    'no-assigning-return-values': require('./rules/no-assigning-return-values'),
+    'unsafe-to-chain-command': require('./rules/unsafe-to-chain-command'),
+    'no-unnecessary-waiting': require('./rules/no-unnecessary-waiting'),
+    'no-async-before': require('./rules/no-async-before'),
+    'no-async-tests': require('./rules/no-async-tests'),
+    'assertion-before-screenshot': require('./rules/assertion-before-screenshot'),
+    'require-data-selectors': require('./rules/require-data-selectors'),
+    'no-force': require('./rules/no-force'),
+    'no-pause': require('./rules/no-pause'),
+  },
+}
+
+const commonGlobals =
+  Object.assign({
+    cy: false,
+    Cypress: false,
+    expect: false,
+    assert: false,
+    chai: false,
+  }, globals.browser, globals.mocha)
+
+const commonLanguageOptions = {
+  ecmaVersion: 2019,
+  sourceType: 'module'
+}
+
+Object.assign(plugin.configs, {
+  globals: {
+    plugins: {
+      cypress: plugin
+    },
+    languageOptions: {
+      globals:
+        commonGlobals,
+      ...commonLanguageOptions
+    }
+  }
+})
+
+Object.assign(plugin.configs, {
+  recommended: {
+    plugins: {
+      cypress: plugin
+    },
+    rules: {
+      'cypress/no-assigning-return-values': 'error',
+      'cypress/no-unnecessary-waiting': 'error',
+      'cypress/no-async-tests': 'error',
+      'cypress/unsafe-to-chain-command': 'error',
+    },
+    languageOptions: {
+      globals:
+        commonGlobals,
+      ...commonLanguageOptions
+    }
+  }
+})
+
+module.exports = plugin
diff --git a/package.json b/package.json
index 181ecc4..4c4bc83 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,11 @@
   "name": "eslint-plugin-cypress",
   "version": "0.0.0-development",
   "description": "An ESLint plugin for projects using Cypress",
-  "main": "index.js",
+  "main": "legacy.js",
+  "exports": {
+      ".": "./legacy.js",
+      "./flat": "./lib/flat.js"
+    },
   "author": "Cypress-io",
   "license": "MIT",
   "keywords": [


Test patch:
diff --git a/tests-legacy/config.js b/tests-legacy/config.js
index 38211c7..e335fa0 100644
--- a/tests-legacy/config.js
+++ b/tests-legacy/config.js
@@ -2,7 +2,7 @@
 'use strict'
 
 const globals = require('globals')
-const config = require('../index.js')
+const config = require('../legacy.js')
 
 describe('environments globals', () => {
   const env = config.environments.globals
diff --git a/tests/config.js b/tests/config.js
index 38211c7..4eea1a0 100644
--- a/tests/config.js
+++ b/tests/config.js
@@ -2,10 +2,10 @@
 'use strict'
 
 const globals = require('globals')
-const config = require('../index.js')
+const config = require('../lib/flat.js')
 
-describe('environments globals', () => {
-  const env = config.environments.globals
+describe('globals languageOptions', () => {
+  const languageOptions = config.configs.globals.languageOptions
 
   it('should not mutate globals', () => {
     expect(globals.browser).not.toHaveProperty('cy')
@@ -13,12 +13,12 @@ describe('environments globals', () => {
   })
 
   it('should include other globals', () => {
-    expect(env.globals).toEqual(expect.objectContaining(globals.browser))
-    expect(env.globals).toEqual(expect.objectContaining(globals.mocha))
+    expect(languageOptions.globals).toEqual(expect.objectContaining(globals.browser))
+    expect(languageOptions.globals).toEqual(expect.objectContaining(globals.mocha))
   })
 
   it('should include cypress globals', () => {
-    expect(env.globals).toEqual(expect.objectContaining({
+    expect(languageOptions.globals).toEqual(expect.objectContaining({
       cy: false,
       Cypress: false,
     }))