diff --git a/docs/generated/packages/eslint-plugin/documents/dependency-checks.md b/docs/generated/packages/eslint-plugin/documents/dependency-checks.md index e50ef5fc73..7a3a3398f3 100644 --- a/docs/generated/packages/eslint-plugin/documents/dependency-checks.md +++ b/docs/generated/packages/eslint-plugin/documents/dependency-checks.md @@ -69,7 +69,7 @@ Sometimes we intentionally want to add or remove a dependency from our `package. "checkObsoleteDependencies": true, // toggle to disable "checkVersionMismatches": true, // toggle to disable "ignoredDependencies": ["lodash"], // these libs will be omitted from checks - "ignoredFiles": ["webpack.config.js", "eslint.config.js"], // list of files that should be skipped for check + "ignoredFiles": ["webpack.config.js", "eslint.config.cjs"], // list of files that should be skipped for check "includeTransitiveDependencies": true, // collect dependencies transitively from children "useLocalPathsForWorkspaceDependencies": true // toggle to disable } diff --git a/docs/generated/packages/eslint/executors/lint.json b/docs/generated/packages/eslint/executors/lint.json index f07d5e02f6..c64e40f969 100644 --- a/docs/generated/packages/eslint/executors/lint.json +++ b/docs/generated/packages/eslint/executors/lint.json @@ -140,7 +140,7 @@ "default": true } }, - "examplesFile": "Linter can be configured in multiple ways. The basic way is to provide only `lintFilePatterns`, which is a mandatory property. This tells us where to look for files to lint.\n\n`project.json`:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Fixing linter issues\" %}\n\nLinter provides an automated way of fixing known issues. To ensure that those changes are properly cached, we need to add an `outputs` property to the `lint` target. Omitting the `outputs` property would produce an invalid cache record. Both of these properties are set by default when scaffolding a new project.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\nWith these settings, we can run the command with a `--fix` flag:\n\n```bash\nnx run frontend:lint --fix\n```\n\nWe can also set this flag via project configuration to always fix files when running lint:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"fix\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Custom output format\" %}\n\nESLint executor uses the `stylish` output format by default. You can change this by specifying the `format` property:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"format\": \"compact\"\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Silence warnings\" %}\n\nMigrated or legacy projects tend to have an overwhelming amount of lint errors. We might want to change those temporarily to be warnings so they don't block the development. But they would still clutter the report. We can run the command with `--quiet` to hide warning (errors would still break the lint):\n\n```bash\nnx run frontend:lint --quiet\n```\n\nWe can also set this via project configuration as a default option.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"quiet\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Flat Config file\" %}\n\n`ESLint` provides several ways of specifying the configuration. The default one is using `.eslintrc.json` but you can override it by setting the `eslintConfig` flag. The new `Flat Config` is now also supported:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"eslintConfig\": \"eslint.config.js\"\n }\n}\n```\n\n**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.js`. Built-in migrations and generators support only `.eslintrc.json` at the moment.\n\n{% /tab %}\n{% /tabs %}\n\n---\n", + "examplesFile": "Linter can be configured in multiple ways. The basic way is to provide only `lintFilePatterns`, which is a mandatory property. This tells us where to look for files to lint.\n\n`project.json`:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Fixing linter issues\" %}\n\nLinter provides an automated way of fixing known issues. To ensure that those changes are properly cached, we need to add an `outputs` property to the `lint` target. Omitting the `outputs` property would produce an invalid cache record. Both of these properties are set by default when scaffolding a new project.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\nWith these settings, we can run the command with a `--fix` flag:\n\n```bash\nnx run frontend:lint --fix\n```\n\nWe can also set this flag via project configuration to always fix files when running lint:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"fix\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Custom output format\" %}\n\nESLint executor uses the `stylish` output format by default. You can change this by specifying the `format` property:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"format\": \"compact\"\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Silence warnings\" %}\n\nMigrated or legacy projects tend to have an overwhelming amount of lint errors. We might want to change those temporarily to be warnings so they don't block the development. But they would still clutter the report. We can run the command with `--quiet` to hide warning (errors would still break the lint):\n\n```bash\nnx run frontend:lint --quiet\n```\n\nWe can also set this via project configuration as a default option.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"quiet\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Flat Config file\" %}\n\n`ESLint` provides several ways of specifying the configuration. The default one is using `.eslintrc.json` but you can override it by setting the `eslintConfig` flag. The new `Flat Config` is now also supported:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"eslintConfig\": \"eslint.config.cjs\"\n }\n}\n```\n\n**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.cjs`. Built-in migrations and generators support only `.eslintrc.json` at the moment.\n\n{% /tab %}\n{% /tabs %}\n\n---\n", "presets": [] }, "hasher": "./src/executors/lint/hasher", diff --git a/docs/shared/packages/eslint/dependency-checks.md b/docs/shared/packages/eslint/dependency-checks.md index e50ef5fc73..7a3a3398f3 100644 --- a/docs/shared/packages/eslint/dependency-checks.md +++ b/docs/shared/packages/eslint/dependency-checks.md @@ -69,7 +69,7 @@ Sometimes we intentionally want to add or remove a dependency from our `package. "checkObsoleteDependencies": true, // toggle to disable "checkVersionMismatches": true, // toggle to disable "ignoredDependencies": ["lodash"], // these libs will be omitted from checks - "ignoredFiles": ["webpack.config.js", "eslint.config.js"], // list of files that should be skipped for check + "ignoredFiles": ["webpack.config.js", "eslint.config.cjs"], // list of files that should be skipped for check "includeTransitiveDependencies": true, // collect dependencies transitively from children "useLocalPathsForWorkspaceDependencies": true // toggle to disable } diff --git a/docs/shared/recipes/tips-n-tricks/migrating-to-flat-eslint.md b/docs/shared/recipes/tips-n-tricks/migrating-to-flat-eslint.md index 6d254d9174..a075d3da03 100644 --- a/docs/shared/recipes/tips-n-tricks/migrating-to-flat-eslint.md +++ b/docs/shared/recipes/tips-n-tricks/migrating-to-flat-eslint.md @@ -10,12 +10,12 @@ See below a direct comparison between `JSON`, `JS` and `Flat` config: {% tabs %} {% tab label="Flat" %} -```js {% fileName="eslint.config.js" %} +```js {% fileName="eslint.config.cjs" %} // the older versions were magically interpreting all the imports // in flat config we do it explicitly const nxPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); -const baseConfig = require('./eslint.base.config.js'); +const baseConfig = require('./eslint.base.config.cjs'); const globals = require('globals'); const jsoncParser = require('jsonc-eslint-parser'); const tsParser = require('@typescript-eslint/parser'); diff --git a/e2e/angular/src/misc.test.ts b/e2e/angular/src/misc.test.ts index a6199b0549..f61900397d 100644 --- a/e2e/angular/src/misc.test.ts +++ b/e2e/angular/src/misc.test.ts @@ -39,7 +39,7 @@ describe('Move Angular Project', () => { expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`); - expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.js`); + expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.cjs`); expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`); expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`); expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`); diff --git a/e2e/angular/src/projects.test.ts b/e2e/angular/src/projects.test.ts index 9a630ef1d9..103f19f6d2 100644 --- a/e2e/angular/src/projects.test.ts +++ b/e2e/angular/src/projects.test.ts @@ -164,13 +164,13 @@ describe('Angular Projects', () => { it('should lint correctly with eslint and handle external HTML files and inline templates', async () => { // disable the prefer-standalone rule for app1 which is not standalone - let app1EslintConfig = readFile(`${app1}/eslint.config.js`); + let app1EslintConfig = readFile(`${app1}/eslint.config.cjs`); app1EslintConfig = app1EslintConfig.replace( `'@angular-eslint/directive-selector': [`, `'@angular-eslint/prefer-standalone': 'off', '@angular-eslint/directive-selector': [` ); - updateFile(`${app1}/eslint.config.js`, app1EslintConfig); + updateFile(`${app1}/eslint.config.cjs`, app1EslintConfig); // check apps and lib pass linting for initial generated code runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`); diff --git a/e2e/eslint/src/linter-legacy.test.ts b/e2e/eslint/src/linter-legacy.test.ts index b561d4419f..f921c94c31 100644 --- a/e2e/eslint/src/linter-legacy.test.ts +++ b/e2e/eslint/src/linter-legacy.test.ts @@ -150,10 +150,10 @@ describe('Linter (legacy)', () => { env: { NX_ADD_PLUGINS: 'false' }, }); checkFilesExist( - 'eslint.config.js', - `apps/${myapp}/eslint.config.js`, - `libs/${mylib}/eslint.config.js`, - `libs/${mylib2}/eslint.config.js` + 'eslint.config.cjs', + `apps/${myapp}/eslint.config.cjs`, + `libs/${mylib}/eslint.config.cjs`, + `libs/${mylib2}/eslint.config.cjs` ); checkFilesDoNotExist( '.eslintrc.json', @@ -164,12 +164,12 @@ describe('Linter (legacy)', () => { // move eslint.config one step up // to test the absence of the flat eslint config in the project root folder - renameFile(`libs/${mylib2}/eslint.config.js`, `libs/eslint.config.js`); + renameFile(`libs/${mylib2}/eslint.config.cjs`, `libs/eslint.config.cjs`); updateFile( - `libs/eslint.config.js`, - readFile(`libs/eslint.config.js`).replace( - `../../eslint.config.js`, - `../eslint.config.js` + `libs/eslint.config.cjs`, + readFile(`libs/eslint.config.cjs`).replace( + `../../eslint.config.cjs`, + `../eslint.config.cjs` ) ); @@ -202,9 +202,9 @@ describe('Linter (legacy)', () => { env: { NX_ADD_PLUGINS: 'false' }, }); checkFilesExist( - 'eslint.config.js', - `${mylib}/eslint.config.js`, - 'eslint.base.config.js' + 'eslint.config.cjs', + `${mylib}/eslint.config.cjs`, + 'eslint.base.config.cjs' ); checkFilesDoNotExist( '.eslintrc.json', diff --git a/e2e/eslint/src/linter.test.ts b/e2e/eslint/src/linter.test.ts index 859dbe9895..f089956b68 100644 --- a/e2e/eslint/src/linter.test.ts +++ b/e2e/eslint/src/linter.test.ts @@ -615,8 +615,8 @@ describe('Linter', () => { runCLI(`generate @nx/js:lib ${jsLib} --linter eslint`); checkFilesExist( - `${reactLib}/eslint.config.js`, - `${jsLib}/eslint.config.js` + `${reactLib}/eslint.config.cjs`, + `${jsLib}/eslint.config.cjs` ); checkFilesDoNotExist( `${reactLib}/.eslintrc.json`, diff --git a/e2e/js/src/js-packaging.test.ts b/e2e/js/src/js-packaging.test.ts index 3f8fae5256..43e92e606a 100644 --- a/e2e/js/src/js-packaging.test.ts +++ b/e2e/js/src/js-packaging.test.ts @@ -178,16 +178,6 @@ describe('packaging libs', () => { `libs/${swcEsmLib}/src/index.ts`, `export * from './lib/${swcEsmLib}.js';` ); - // We also need to update the eslint config file extensions to be explicitly commonjs - // TODO: re-evaluate this once we support ESM eslint configs - renameFile( - `libs/${tscEsmLib}/eslint.config.js`, - `libs/${tscEsmLib}/eslint.config.cjs` - ); - renameFile( - `libs/${swcEsmLib}/eslint.config.js`, - `libs/${swcEsmLib}/eslint.config.cjs` - ); // Add additional entry points for `exports` field updateJson(join('libs', tscLib, 'project.json'), (json) => { diff --git a/e2e/nx/src/__snapshots__/extras.test.ts.snap b/e2e/nx/src/__snapshots__/extras.test.ts.snap index fbc8c2974e..c06c81383b 100644 --- a/e2e/nx/src/__snapshots__/extras.test.ts.snap +++ b/e2e/nx/src/__snapshots__/extras.test.ts.snap @@ -26,7 +26,7 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent ], "lib-base-123": [ "libs/lib-base-123/README.md", - "libs/lib-base-123/eslint.config.js", + "libs/lib-base-123/eslint.config.cjs", "libs/lib-base-123/jest.config.ts", "libs/lib-base-123/package.json", "libs/lib-base-123/project.json", @@ -39,7 +39,7 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent ], "lib-dependent-123": [ "libs/lib-dependent-123/README.md", - "libs/lib-dependent-123/eslint.config.js", + "libs/lib-dependent-123/eslint.config.cjs", "libs/lib-dependent-123/jest.config.ts", "libs/lib-dependent-123/package.json", "libs/lib-dependent-123/project.json", diff --git a/packages/angular/src/generators/library/library.spec.ts b/packages/angular/src/generators/library/library.spec.ts index 64253f5de7..b49e802c81 100644 --- a/packages/angular/src/generators/library/library.spec.ts +++ b/packages/angular/src/generators/library/library.spec.ts @@ -1204,14 +1204,14 @@ describe('lib', () => { describe('--linter', () => { describe('eslint', () => { it('should add valid eslint JSON configuration which extends from Nx presets (flat config)', async () => { - tree.write('eslint.config.js', ''); + tree.write('eslint.config.cjs', ''); await runLibraryGeneratorWithOpts({ linter: Linter.EsLint }); - const eslintConfig = tree.read('my-lib/eslint.config.js', 'utf-8'); + const eslintConfig = tree.read('my-lib/eslint.config.cjs', 'utf-8'); expect(eslintConfig).toMatchInlineSnapshot(` "const nx = require("@nx/eslint-plugin"); - const baseConfig = require("../eslint.config.js"); + const baseConfig = require("../eslint.config.cjs"); module.exports = [ ...baseConfig, diff --git a/packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap b/packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap index 9b8175483a..92ad21afdc 100644 --- a/packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap +++ b/packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap @@ -93,7 +93,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = ` "!{projectRoot}/**/*.spec.[jt]s", "!{projectRoot}/karma.conf.js", "!{projectRoot}/.eslintrc.json", - "!{projectRoot}/eslint.config.js", + "!{projectRoot}/eslint.config.cjs", ], "sharedGlobals": [], }, @@ -104,7 +104,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = ` "default", "{workspaceRoot}/.eslintrc.json", "{workspaceRoot}/.eslintignore", - "{workspaceRoot}/eslint.config.js", + "{workspaceRoot}/eslint.config.cjs", ], }, "build": { @@ -129,7 +129,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = ` "inputs": [ "default", "{workspaceRoot}/.eslintrc.json", - "{workspaceRoot}/eslint.config.js", + "{workspaceRoot}/eslint.config.cjs", ], }, "test": { diff --git a/packages/angular/src/generators/ng-add/utilities/workspace.ts b/packages/angular/src/generators/ng-add/utilities/workspace.ts index 492a351fca..21af2b16cf 100644 --- a/packages/angular/src/generators/ng-add/utilities/workspace.ts +++ b/packages/angular/src/generators/ng-add/utilities/workspace.ts @@ -64,7 +64,10 @@ export function createNxJson( ] : []), ...(targets.lint - ? ['!{projectRoot}/.eslintrc.json', '!{projectRoot}/eslint.config.js'] + ? [ + '!{projectRoot}/.eslintrc.json', + '!{projectRoot}/eslint.config.cjs', + ] : []), ].filter(Boolean), }, @@ -85,7 +88,7 @@ export function createNxJson( inputs: [ 'default', '{workspaceRoot}/.eslintrc.json', - '{workspaceRoot}/eslint.config.js', + '{workspaceRoot}/eslint.config.cjs', ], cache: true, } diff --git a/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ b/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ index 3c711de7a2..cda568d129 100644 --- a/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ +++ b/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ @@ -16,5 +16,5 @@ <%_ if (jsx) { _%>"<%= offsetFromProjectRoot %>**/*.cy.jsx",<%_ } _%> "<%= offsetFromProjectRoot %>**/*.d.ts" ], - "exclude": ["out-tsc", "test-output"<% if (linter === 'eslint') { %>, "eslint.config.js"<% } %>] + "exclude": ["out-tsc", "test-output"<% if (linter === 'eslint') { %>, "eslint.config.js", "eslint.config.cjs", "eslint.config.mjs"<% } %>] } diff --git a/packages/eslint-plugin/src/flat-configs/javascript.ts b/packages/eslint-plugin/src/flat-configs/javascript.ts index aad10376c9..5198a6f410 100644 --- a/packages/eslint-plugin/src/flat-configs/javascript.ts +++ b/packages/eslint-plugin/src/flat-configs/javascript.ts @@ -24,7 +24,7 @@ const isPrettierAvailable = */ export default tseslint.config( { - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], extends: [eslint.configs.recommended, ...tseslint.configs.recommended], }, { @@ -40,7 +40,7 @@ export default tseslint.config( plugins: { '@typescript-eslint': tseslint.plugin }, }, { - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { '@typescript-eslint/explicit-member-accessibility': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/eslint-plugin/src/flat-configs/typescript.ts b/packages/eslint-plugin/src/flat-configs/typescript.ts index 58dc9e06b5..099641cb65 100644 --- a/packages/eslint-plugin/src/flat-configs/typescript.ts +++ b/packages/eslint-plugin/src/flat-configs/typescript.ts @@ -15,7 +15,7 @@ const isPrettierAvailable = */ export default tseslint.config( { - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], extends: [eslint.configs.recommended, ...tseslint.configs.recommended], }, { @@ -30,7 +30,7 @@ export default tseslint.config( }, }, { - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', , '**/*.cts', '**/*.mts'], rules: { '@typescript-eslint/explicit-member-accessibility': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/eslint/docs/eslint-examples.md b/packages/eslint/docs/eslint-examples.md index 03f7651a5e..4830039142 100644 --- a/packages/eslint/docs/eslint-examples.md +++ b/packages/eslint/docs/eslint-examples.md @@ -96,12 +96,12 @@ We can also set this via project configuration as a default option. "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/frontend/**/*.ts"], - "eslintConfig": "eslint.config.js" + "eslintConfig": "eslint.config.cjs" } } ``` -**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.js`. Built-in migrations and generators support only `.eslintrc.json` at the moment. +**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.cjs`. Built-in migrations and generators support only `.eslintrc.json` at the moment. {% /tab %} {% /tabs %} diff --git a/packages/eslint/migrations.json b/packages/eslint/migrations.json index c9a2ee1aac..0be15e3c7a 100644 --- a/packages/eslint/migrations.json +++ b/packages/eslint/migrations.json @@ -24,6 +24,11 @@ "version": "20.2.0-beta.5", "description": "Update TypeScript ESLint packages to v8.13.0 if they are already on v8", "implementation": "./src/migrations/update-20-2-0/update-typescript-eslint-v8-13-0" + }, + "add-file-extensions-to-overrides": { + "version": "20.3.0-beta.1", + "description": "Update ESLint flat config to include .cjs, .mjs, .cts, and .mts files in overrides (if needed)", + "implementation": "./src/migrations/update-20-3-0/add-file-extensions-to-overrides" } }, "packageJsonUpdates": { diff --git a/packages/eslint/src/executors/lint/lint.impl.spec.ts b/packages/eslint/src/executors/lint/lint.impl.spec.ts index ca460f1d79..23067df279 100644 --- a/packages/eslint/src/executors/lint/lint.impl.spec.ts +++ b/packages/eslint/src/executors/lint/lint.impl.spec.ts @@ -436,7 +436,7 @@ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message logging the found flat config', async () => { setupMocks(); - tempFs.createFileSync('apps/proj/eslint.config.js', ''); + tempFs.createFileSync('apps/proj/eslint.config.cjs', ''); tempFs.createFileSync('apps/proj/src/some-file.ts', ''); mockLintFiles.mockImplementation(() => { @@ -456,7 +456,7 @@ Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts` ); expect(console.error).toHaveBeenCalledWith( ` -Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "apps/proj/eslint.config.js" +Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "apps/proj/eslint.config.cjs" Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue. @@ -466,7 +466,7 @@ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message logging the found flat config at the workspace root', async () => { setupMocks(); - tempFs.createFileSync('eslint.config.js', ''); + tempFs.createFileSync('eslint.config.cjs', ''); tempFs.createFileSync('apps/proj/src/some-file.ts', ''); mockLintFiles.mockImplementation(() => { @@ -486,7 +486,7 @@ Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts` ); expect(console.error).toHaveBeenCalledWith( ` -Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "eslint.config.js" +Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "eslint.config.cjs" Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue. @@ -906,15 +906,15 @@ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how expect(result).toEqual({ success: true }); }); - it('should pass path to eslint.config.js to resolveAndInstantiateESLint if it is unspecified and we are using flag configuration', async () => { + it('should pass path to eslint.config.cjs to resolveAndInstantiateESLint if it is unspecified and we are using flag configuration', async () => { setupMocks(); jest.spyOn(fs, 'existsSync').mockReturnValue(true); await lintExecutor(createValidRunBuilderOptions(), mockContext); expect(mockResolveAndInstantiateESLint).toHaveBeenCalledWith( - `${mockContext.root}/apps/proj/eslint.config.js`, + `${mockContext.root}/apps/proj/eslint.config.cjs`, { lintFilePatterns: [], - eslintConfig: 'apps/proj/eslint.config.js', + eslintConfig: 'apps/proj/eslint.config.cjs', fix: true, cache: true, cacheLocation: 'cacheLocation1/proj', diff --git a/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts b/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts index a4cb7ed8f1..abc48ee24e 100644 --- a/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts +++ b/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts @@ -162,7 +162,7 @@ describe('eslint-utils', () => { }); describe('ESLint Flat Config', () => { - it('should throw if a non eslint.config.js or eslint.config.cjs file is used with ESLint Flat Config', async () => { + it('should throw if a non eslint.config.cjs or eslint.config.cjs file is used with ESLint Flat Config', async () => { await expect( resolveAndInstantiateESLint('./.eslintrc.json', {} as any, true) ).rejects.toThrowErrorMatchingInlineSnapshot( diff --git a/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap b/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap index 9a5d471e3e..9ecee467df 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap +++ b/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap @@ -12,6 +12,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, { @@ -38,7 +41,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -49,7 +52,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -70,6 +73,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { languageOptions: { @@ -100,7 +106,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -111,7 +117,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -131,6 +137,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { languageOptions: { globals: { myCustomGlobal: 'readonly' } } }, { @@ -157,7 +166,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -168,7 +177,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -188,6 +197,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -213,7 +225,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -224,7 +236,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -248,6 +260,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { languageOptions: { parser: typescriptEslintParser } }, { @@ -274,7 +289,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -285,7 +300,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -308,6 +323,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { 'eslint-plugin-import': eslintPluginImport, @@ -340,7 +358,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -351,7 +369,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -371,6 +389,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { settings: { @@ -401,7 +422,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -412,7 +433,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -432,6 +453,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -457,7 +481,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -468,7 +492,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -478,9 +502,12 @@ module.exports = [ `; exports[`convert-to-flat-config generator should convert json successfully 2`] = ` -"const baseConfig = require('../../eslint.config.js'); +"const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + { + ignores: ['**/dist'], + }, ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -512,6 +539,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -537,7 +567,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -548,7 +578,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -558,9 +588,12 @@ module.exports = [ `; exports[`convert-to-flat-config generator should convert yaml successfully 2`] = ` -"const baseConfig = require('../../eslint.config.js'); +"const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + { + ignores: ['**/dist'], + }, ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -592,6 +625,9 @@ const compat = new FlatCompat({ }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -617,7 +653,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -628,7 +664,7 @@ module.exports = [ }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -638,9 +674,12 @@ module.exports = [ `; exports[`convert-to-flat-config generator should convert yml successfully 2`] = ` -"const baseConfig = require('../../eslint.config.js'); +"const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + { + ignores: ['**/dist'], + }, ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -662,9 +701,12 @@ module.exports = [ `; exports[`convert-to-flat-config generator should handle custom eslintignores 1`] = ` -"const baseConfig = require('../../eslint.config.js'); +"const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + { + ignores: ['**/dist'], + }, ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], diff --git a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts index 5e976f5294..335c3a3e72 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts @@ -40,6 +40,12 @@ describe('convertEslintJsonToFlatConfig', () => { extends: ['plugin:@nx/typescript'], rules: {}, }, + { + files: ['*.js', '*.jsx'], + extends: ['plugin:@nx/javascript'], + rules: {}, + }, + { files: [ '**/*.spec.ts', @@ -76,6 +82,11 @@ describe('convertEslintJsonToFlatConfig', () => { }); module.exports = [ + { + ignores: [ + "**/dist" + ] + }, { plugins: { "@nx": nxEslintPlugin } }, { files: [ @@ -110,7 +121,25 @@ describe('convertEslintJsonToFlatConfig', () => { ...config, files: [ "**/*.ts", - "**/*.tsx" + "**/*.tsx", + "**/*.cts", + "**/*.mts" + ], + rules: { + ...config.rules + } + })), + ...compat.config({ + extends: [ + "plugin:@nx/javascript" + ] + }).map(config => ({ + ...config, + files: [ + "**/*.js", + "**/*.jsx", + "**/*.cjs", + "**/*.mjs" ], rules: { ...config.rules @@ -205,7 +234,7 @@ describe('convertEslintJsonToFlatConfig', () => { expect(content).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const globals = require("globals"); const compat = new FlatCompat({ @@ -214,6 +243,11 @@ describe('convertEslintJsonToFlatConfig', () => { }); module.exports = [ + { + ignores: [ + "**/dist" + ] + }, ...baseConfig, ...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"), { languageOptions: { globals: { ...globals.jest } } }, diff --git a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts index 0421c89236..d3cfd4a0ff 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts @@ -30,6 +30,13 @@ export function convertEslintJsonToFlatConfig( let combinedConfig: ts.PropertyAssignment[] = []; let languageOptions: ts.PropertyAssignment[] = []; + // exclude dist and eslint config from being linted, which matches the default for new workspaces + exportElements.push( + generateAst({ + ignores: ['**/dist'], + }) + ); + if (config.extends) { const extendsResult = addExtends(importsMap, exportElements, config); isFlatCompatNeeded = extendsResult.isFlatCompatNeeded; @@ -218,7 +225,7 @@ function addExtends( configBlocks.push(generateSpreadElement(localName)); const newImport = imp.replace( /^(.*)\.eslintrc(.base)?\.json$/, - '$1eslint$2.config.js' + '$1eslint$2.config.cjs' ); importsMap.set(newImport, localName); } else { diff --git a/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts b/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts index ae0a73e12b..dbdc9cffac 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts @@ -82,19 +82,19 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.exists('eslint.config.js')).toBeTruthy(); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy(); + expect(tree.exists('eslint.config.cjs')).toBeTruthy(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy(); expect( - tree.read('libs/test-lib/eslint.config.js', 'utf-8') + tree.read('libs/test-lib/eslint.config.cjs', 'utf-8') ).toMatchSnapshot(); // check nx.json changes const nxJson = readJson(tree, 'nx.json'); expect(nxJson.targetDefaults.lint.inputs).toContain( - '{workspaceRoot}/eslint.config.js' + '{workspaceRoot}/eslint.config.cjs' ); expect(nxJson.namedInputs.production).toContain( - '!{projectRoot}/eslint.config.js' + '!{projectRoot}/eslint.config.cjs' ); }); @@ -112,19 +112,19 @@ describe('convert-to-flat-config generator', () => { await convertToFlatConfigGenerator(tree, options); - expect(tree.exists('eslint.config.js')).toBeTruthy(); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy(); + expect(tree.exists('eslint.config.cjs')).toBeTruthy(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy(); expect( - tree.read('libs/test-lib/eslint.config.js', 'utf-8') + tree.read('libs/test-lib/eslint.config.cjs', 'utf-8') ).toMatchSnapshot(); // check nx.json changes const nxJson = readJson(tree, 'nx.json'); expect(nxJson.targetDefaults.lint.inputs).toContain( - '{workspaceRoot}/eslint.config.js' + '{workspaceRoot}/eslint.config.cjs' ); expect(nxJson.namedInputs.production).toContain( - '!{projectRoot}/eslint.config.js' + '!{projectRoot}/eslint.config.cjs' ); }); @@ -142,19 +142,19 @@ describe('convert-to-flat-config generator', () => { await convertToFlatConfigGenerator(tree, options); - expect(tree.exists('eslint.config.js')).toBeTruthy(); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy(); + expect(tree.exists('eslint.config.cjs')).toBeTruthy(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy(); expect( - tree.read('libs/test-lib/eslint.config.js', 'utf-8') + tree.read('libs/test-lib/eslint.config.cjs', 'utf-8') ).toMatchSnapshot(); // check nx.json changes const nxJson = readJson(tree, 'nx.json'); expect(nxJson.targetDefaults.lint.inputs).toContain( - '{workspaceRoot}/eslint.config.js' + '{workspaceRoot}/eslint.config.cjs' ); expect(nxJson.namedInputs.production).toContain( - '!{projectRoot}/eslint.config.js' + '!{projectRoot}/eslint.config.cjs' ); }); @@ -171,7 +171,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(` "const { FlatCompat } = require('@eslint/eslintrc'); const js = require('@eslint/js'); const nxEslintPlugin = require('@nx/eslint-plugin'); @@ -182,6 +182,9 @@ describe('convert-to-flat-config generator', () => { }); module.exports = [ + { + ignores: ['**/dist'], + }, ...compat.extends('plugin:storybook/recommended'), { plugins: { '@nx': nxEslintPlugin } }, { @@ -208,7 +211,7 @@ describe('convert-to-flat-config generator', () => { }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -219,7 +222,7 @@ describe('convert-to-flat-config generator', () => { }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -227,11 +230,14 @@ describe('convert-to-flat-config generator', () => { ]; " `); - expect(tree.read('libs/test-lib/eslint.config.js', 'utf-8')) + expect(tree.read('libs/test-lib/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` - "const baseConfig = require('../../eslint.config.js'); + "const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + { + ignores: ['**/dist'], + }, ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], @@ -266,7 +272,7 @@ describe('convert-to-flat-config generator', () => { tree.write('.eslintignore', 'ignore/me'); await convertToFlatConfigGenerator(tree, options); - const config = tree.read('eslint.config.js', 'utf-8'); + const config = tree.read('eslint.config.cjs', 'utf-8'); expect(config).toContain('ignore/me'); expect(config).toMatchSnapshot(); expect(tree.exists('.eslintignore')).toBeFalsy(); @@ -290,7 +296,7 @@ describe('convert-to-flat-config generator', () => { await convertToFlatConfigGenerator(tree, options); expect( - tree.read('libs/test-lib/eslint.config.js', 'utf-8') + tree.read('libs/test-lib/eslint.config.cjs', 'utf-8') ).toMatchSnapshot(); expect(tree.exists('another-folder/.myeslintignore')).toBeFalsy(); expect(tree.exists('libs/test-lib/.eslintignore')).toBeFalsy(); @@ -316,7 +322,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); }); it('should add env configuration', async () => { @@ -335,7 +341,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); }); it('should add global configuration', async () => { @@ -353,7 +359,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); }); it('should add global and env configuration', async () => { @@ -374,7 +380,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); }); it('should add plugins', async () => { @@ -395,7 +401,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); }); it('should add parser', async () => { @@ -411,7 +417,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot(); }); it('should add linter options', async () => { @@ -427,7 +433,7 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(` "const { FlatCompat } = require('@eslint/eslintrc'); const js = require('@eslint/js'); const nxEslintPlugin = require('@nx/eslint-plugin'); @@ -438,6 +444,9 @@ describe('convert-to-flat-config generator', () => { }); module.exports = [ + { + ignores: ['**/dist'], + }, { plugins: { '@nx': nxEslintPlugin } }, { linterOptions: { @@ -468,7 +477,7 @@ describe('convert-to-flat-config generator', () => { }) .map((config) => ({ ...config, - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], rules: { ...config.rules, }, @@ -479,7 +488,7 @@ describe('convert-to-flat-config generator', () => { }) .map((config) => ({ ...config, - files: ['**/*.js', '**/*.jsx'], + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], rules: { ...config.rules, }, @@ -510,11 +519,11 @@ describe('convert-to-flat-config generator', () => { } ); - expect(tree.exists('eslint.config.js')).toBeFalsy(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeFalsy(); + expect(tree.exists('eslint.config.cjs')).toBeFalsy(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeFalsy(); await convertToFlatConfigGenerator(tree, options); - expect(tree.exists('eslint.config.js')).toBeTruthy(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy(); + expect(tree.exists('eslint.config.cjs')).toBeTruthy(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy(); }); it('should convert project if target is defined via plugin as object', async () => { @@ -545,11 +554,11 @@ describe('convert-to-flat-config generator', () => { } ); - expect(tree.exists('eslint.config.js')).toBeFalsy(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeFalsy(); + expect(tree.exists('eslint.config.cjs')).toBeFalsy(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeFalsy(); await convertToFlatConfigGenerator(tree, options); - expect(tree.exists('eslint.config.js')).toBeTruthy(); - expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy(); + expect(tree.exists('eslint.config.cjs')).toBeTruthy(); + expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy(); }); it('should handle parser options even if parser is extended', async () => { @@ -589,13 +598,16 @@ describe('convert-to-flat-config generator', () => { }); await convertToFlatConfigGenerator(tree, options); - expect(tree.exists('apps/dx-assets-ui/eslint.config.js')).toBeTruthy(); - expect(tree.exists('eslint.config.js')).toBeTruthy(); - expect(tree.read('apps/dx-assets-ui/eslint.config.js', 'utf-8')) + expect(tree.exists('apps/dx-assets-ui/eslint.config.cjs')).toBeTruthy(); + expect(tree.exists('eslint.config.cjs')).toBeTruthy(); + expect(tree.read('apps/dx-assets-ui/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` - "const baseConfig = require('../../eslint.config.js'); + "const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + { + ignores: ['**/dist'], + }, ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], diff --git a/packages/eslint/src/generators/convert-to-flat-config/generator.ts b/packages/eslint/src/generators/convert-to-flat-config/generator.ts index 5e68663fda..1cfbc7a527 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/generator.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/generator.ts @@ -42,10 +42,10 @@ export async function convertToFlatConfigGenerator( const eslintIgnoreFiles = new Set(['.eslintignore']); - // convert root eslint config to eslint.config.js + // convert root eslint config to eslint.config.cjs convertRootToFlatConfig(tree, eslintFile); - // convert project eslint files to eslint.config.js + // convert project eslint files to eslint.config.cjs const projects = getProjects(tree); for (const [project, projectConfig] of projects) { convertProjectToFlatConfig( @@ -77,13 +77,13 @@ export default convertToFlatConfigGenerator; function convertRootToFlatConfig(tree: Tree, eslintFile: string) { if (/\.base\.(js|json|yml|yaml)$/.test(eslintFile)) { - convertConfigToFlatConfig(tree, '', eslintFile, 'eslint.base.config.js'); + convertConfigToFlatConfig(tree, '', eslintFile, 'eslint.base.config.cjs'); } convertConfigToFlatConfig( tree, '', eslintFile.replace('.base.', '.'), - 'eslint.config.js' + 'eslint.config.cjs' ); } @@ -132,7 +132,7 @@ function convertProjectToFlatConfig( tree, projectConfig.root, eslintFile, - 'eslint.config.js', + 'eslint.config.cjs', ignorePath ); eslintIgnoreFiles.add(`${projectConfig.root}/.eslintignore`); @@ -151,17 +151,17 @@ function updateNxJsonConfig(tree: Tree) { updateJson(tree, 'nx.json', (json: NxJsonConfiguration) => { if (json.targetDefaults?.lint?.inputs) { const inputSet = new Set(json.targetDefaults.lint.inputs); - inputSet.add('{workspaceRoot}/eslint.config.js'); + inputSet.add('{workspaceRoot}/eslint.config.cjs'); json.targetDefaults.lint.inputs = Array.from(inputSet); } if (json.targetDefaults?.['@nx/eslint:lint']?.inputs) { const inputSet = new Set(json.targetDefaults['@nx/eslint:lint'].inputs); - inputSet.add('{workspaceRoot}/eslint.config.js'); + inputSet.add('{workspaceRoot}/eslint.config.cjs'); json.targetDefaults['@nx/eslint:lint'].inputs = Array.from(inputSet); } if (json.namedInputs?.production) { const inputSet = new Set(json.namedInputs.production); - inputSet.add('!{projectRoot}/eslint.config.js'); + inputSet.add('!{projectRoot}/eslint.config.cjs'); json.namedInputs.production = Array.from(inputSet); } return json; diff --git a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts index a482c80ce9..cd4b550961 100644 --- a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts +++ b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts @@ -573,7 +573,7 @@ describe('Eslint - Convert Executors To Plugin', () => { 'default', '{projectRoot}/.eslintrc.json', '{projectRoot}/.eslintignore', - '{projectRoot}/eslint.config.js', + '{projectRoot}/eslint.config.cjs', ], }; updateNxJson(tree, nxJson); @@ -589,7 +589,7 @@ describe('Eslint - Convert Executors To Plugin', () => { 'default', '{projectRoot}/.eslintrc.json', '{projectRoot}/.eslintignore', - '{projectRoot}/eslint.config.js', + '{projectRoot}/eslint.config.cjs', { externalDependencies: ['eslint'] }, ]); }); @@ -604,7 +604,7 @@ describe('Eslint - Convert Executors To Plugin', () => { 'default', '{projectRoot}/.eslintrc.json', '{projectRoot}/.eslintignore', - '{projectRoot}/eslint.config.js', + '{projectRoot}/eslint.config.cjs', { externalDependencies: ['eslint-plugin-react'] }, ], }; @@ -621,7 +621,7 @@ describe('Eslint - Convert Executors To Plugin', () => { 'default', '{projectRoot}/.eslintrc.json', '{projectRoot}/.eslintignore', - '{projectRoot}/eslint.config.js', + '{projectRoot}/eslint.config.cjs', { externalDependencies: ['eslint-plugin-react', 'eslint'] }, ]); }); @@ -636,7 +636,7 @@ describe('Eslint - Convert Executors To Plugin', () => { 'default', '{projectRoot}/.eslintrc.json', '{projectRoot}/.eslintignore', - '{projectRoot}/eslint.config.js', + '{projectRoot}/eslint.config.cjs', { externalDependencies: ['eslint', 'eslint-plugin-react'] }, ], }; @@ -653,7 +653,7 @@ describe('Eslint - Convert Executors To Plugin', () => { 'default', '{projectRoot}/.eslintrc.json', '{projectRoot}/.eslintignore', - '{projectRoot}/eslint.config.js', + '{projectRoot}/eslint.config.cjs', { externalDependencies: ['eslint', 'eslint-plugin-react'] }, ]); }); diff --git a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts index a3237fbb8f..c50ea64455 100644 --- a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts +++ b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts @@ -65,7 +65,7 @@ function postTargetTransformer( 'default', '{workspaceRoot}/.eslintrc.json', '{workspaceRoot}/.eslintignore', - '{workspaceRoot}/eslint.config.js', + '{workspaceRoot}/eslint.config.cjs', ].includes(input) ); if (inputs.length === 0) { diff --git a/packages/eslint/src/generators/init/global-eslint-config.ts b/packages/eslint/src/generators/init/global-eslint-config.ts index 1e2ad3efe0..e384918a2d 100644 --- a/packages/eslint/src/generators/init/global-eslint-config.ts +++ b/packages/eslint/src/generators/init/global-eslint-config.ts @@ -114,7 +114,9 @@ export const getGlobalFlatEslintConfiguration = ( content = addBlockToFlatConfigExport( content, - generateFlatOverride({ ignores: ['**/dist'] }) + generateFlatOverride({ + ignores: ['**/dist'], + }) ); if (!rootProject) { @@ -145,7 +147,14 @@ export const getGlobalFlatEslintConfiguration = ( content = addBlockToFlatConfigExport( content, generateFlatOverride({ - files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + files: [ + '**/*.ts', + '**/*.tsx', + '**/*.js', + '**/*.jsx', + '**/*.cjs', + '**/*.mjs', + ], rules: {}, }) ); diff --git a/packages/eslint/src/generators/init/init-migration.ts b/packages/eslint/src/generators/init/init-migration.ts index d304973c81..6e43b7a861 100644 --- a/packages/eslint/src/generators/init/init-migration.ts +++ b/packages/eslint/src/generators/init/init-migration.ts @@ -57,9 +57,9 @@ export function migrateConfigToMonorepoStyle( keepExistingVersions ); tree.write( - tree.exists('eslint.config.js') - ? 'eslint.base.config.js' - : 'eslint.config.js', + tree.exists('eslint.config.cjs') + ? 'eslint.base.config.cjs' + : 'eslint.config.cjs', getGlobalFlatEslintConfiguration() ); } else { @@ -134,7 +134,7 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) { let config = tree.read(projectEslintPath, 'utf-8'); // remove @nx plugin config = removePlugin(config, '@nx', '@nx/eslint-plugin-nx'); - // extend eslint.base.config.js + // extend eslint.base.config.cjs config = addImportToFlatConfig( config, 'baseConfig', diff --git a/packages/eslint/src/generators/init/init.spec.ts b/packages/eslint/src/generators/init/init.spec.ts index 57f87c29d4..a43b6935e4 100644 --- a/packages/eslint/src/generators/init/init.spec.ts +++ b/packages/eslint/src/generators/init/init.spec.ts @@ -113,7 +113,7 @@ describe('@nx/eslint:init', () => { 'default', '{workspaceRoot}/.eslintrc.json', '{workspaceRoot}/.eslintignore', - '{workspaceRoot}/eslint.config.js', + '{workspaceRoot}/eslint.config.cjs', ], }); }); @@ -137,7 +137,7 @@ describe('@nx/eslint:init', () => { 'default', '{workspaceRoot}/.eslintrc.json', '{workspaceRoot}/.eslintignore', - '{workspaceRoot}/eslint.config.js', + '{workspaceRoot}/eslint.config.cjs', ], }); }); diff --git a/packages/eslint/src/generators/init/init.ts b/packages/eslint/src/generators/init/init.ts index 7eedb55492..f297352d56 100644 --- a/packages/eslint/src/generators/init/init.ts +++ b/packages/eslint/src/generators/init/init.ts @@ -28,7 +28,7 @@ function updateProductionFileset(tree: Tree) { const productionFileSet = nxJson.namedInputs?.production; if (productionFileSet) { productionFileSet.push('!{projectRoot}/.eslintrc.json'); - productionFileSet.push('!{projectRoot}/eslint.config.js'); + productionFileSet.push('!{projectRoot}/eslint.config.cjs'); // Dedupe and set nxJson.namedInputs.production = Array.from(new Set(productionFileSet)); } @@ -45,7 +45,7 @@ function addTargetDefaults(tree: Tree) { 'default', `{workspaceRoot}/.eslintrc.json`, `{workspaceRoot}/.eslintignore`, - `{workspaceRoot}/eslint.config.js`, + `{workspaceRoot}/eslint.config.cjs`, ]; updateNxJson(tree, nxJson); } diff --git a/packages/eslint/src/generators/lint-project/lint-project.spec.ts b/packages/eslint/src/generators/lint-project/lint-project.spec.ts index 3fa984d3cc..2c194491af 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.spec.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.spec.ts @@ -53,7 +53,7 @@ describe('@nx/eslint:lint-project', () => { skipFormat: true, }); - expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` + expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(` "const nx = require("@nx/eslint-plugin"); module.exports = [ @@ -97,7 +97,9 @@ describe('@nx/eslint:lint-project', () => { "**/*.ts", "**/*.tsx", "**/*.js", - "**/*.jsx" + "**/*.jsx", + "**/*.cjs", + "**/*.mjs" ], // Override or add rules here rules: {} diff --git a/packages/eslint/src/generators/lint-project/lint-project.ts b/packages/eslint/src/generators/lint-project/lint-project.ts index 48fa7b4d00..b637ea3fcc 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.ts @@ -273,8 +273,7 @@ function createEsLintConfiguration( }); const nodeList = createNodeList(importMap, nodes); const content = stringifyNodeList(nodeList); - const ext = extendedRootConfig?.endsWith('.cjs') ? '.cjs' : '.js'; - tree.write(join(projectConfig.root, `eslint.config${ext}`), content); + tree.write(join(projectConfig.root, `eslint.config.cjs`), content); } else { writeJson(tree, join(projectConfig.root, `.eslintrc.json`), { extends: extendedRootConfig ? [pathToRootConfig] : undefined, diff --git a/packages/eslint/src/generators/lint-project/setup-root-eslint.ts b/packages/eslint/src/generators/lint-project/setup-root-eslint.ts index 6a486e7f71..3de9e0f99b 100644 --- a/packages/eslint/src/generators/lint-project/setup-root-eslint.ts +++ b/packages/eslint/src/generators/lint-project/setup-root-eslint.ts @@ -71,7 +71,7 @@ function setUpLegacyRootEslintRc(tree: Tree, options: SetupRootEsLintOptions) { function setUpRootFlatConfig(tree: Tree, options: SetupRootEsLintOptions) { tree.write( - 'eslint.config.js', + 'eslint.config.cjs', getGlobalFlatEslintConfiguration(options.rootProject) ); diff --git a/packages/eslint/src/generators/utils/eslint-file.spec.ts b/packages/eslint/src/generators/utils/eslint-file.spec.ts index 619128da67..dd5976b082 100644 --- a/packages/eslint/src/generators/utils/eslint-file.spec.ts +++ b/packages/eslint/src/generators/utils/eslint-file.spec.ts @@ -122,10 +122,10 @@ describe('@nx/eslint:lint-file', () => { }); it('should add extends to flat config', () => { - tree.write('eslint.config.js', 'module.exports = {};'); + tree.write('eslint.config.cjs', 'module.exports = {};'); tree.write( - 'apps/demo/eslint.config.js', - `const baseConfig = require("../../eslint.config.js"); + 'apps/demo/eslint.config.cjs', + `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, @@ -143,11 +143,11 @@ module.exports = [ addExtendsToLintConfig(tree, 'apps/demo', 'plugin:playwright/recommend'); - expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -177,10 +177,10 @@ module.exports = [ packageJson: { name: 'eslint', version: '9.0.0' }, path: '', }); - tree.write('eslint.config.js', 'module.exports = {};'); + tree.write('eslint.config.cjs', 'module.exports = {};'); tree.write( - 'apps/demo/eslint.config.js', - `const baseConfig = require("../../eslint.config.js"); + 'apps/demo/eslint.config.cjs', + `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, @@ -201,12 +201,12 @@ module.exports = [ needCompatFixup: true, }); - expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); const { fixupConfigRules } = require("@eslint/compat"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -236,10 +236,10 @@ module.exports = [ packageJson: { name: 'eslint', version: '9.0.0' }, path: '', }); - tree.write('eslint.config.js', 'module.exports = {};'); + tree.write('eslint.config.cjs', 'module.exports = {};'); tree.write( - 'apps/demo/eslint.config.js', - `const baseConfig = require("../../eslint.config.js"); + 'apps/demo/eslint.config.cjs', + `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, @@ -264,12 +264,12 @@ module.exports = [ { name: 'incompatible-plugin3', needCompatFixup: true }, ]); - expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); const { fixupConfigRules } = require("@eslint/compat"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -307,10 +307,10 @@ module.exports = [ packageJson: { name: 'eslint', version: '8.0.0' }, path: '', }); - tree.write('eslint.config.js', 'module.exports = {};'); + tree.write('eslint.config.cjs', 'module.exports = {};'); tree.write( - 'apps/demo/eslint.config.js', - `const baseConfig = require("../../eslint.config.js"); + 'apps/demo/eslint.config.cjs', + `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, @@ -331,11 +331,11 @@ module.exports = [ needCompatFixup: true, }); - expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -362,10 +362,10 @@ module.exports = [ describe('replaceOverridesInLintConfig', () => { it('should replace overrides when using flat config', () => { - tree.write('eslint.config.js', 'module.exports = {};'); + tree.write('eslint.config.cjs', 'module.exports = {};'); tree.write( - 'apps/demo/eslint.config.js', - `const baseConfig = require("../../eslint.config.js"); + 'apps/demo/eslint.config.cjs', + `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, @@ -428,11 +428,11 @@ module.exports = [ }, ]); - expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, diff --git a/packages/eslint/src/generators/utils/eslint-file.ts b/packages/eslint/src/generators/utils/eslint-file.ts index e593d205eb..b85d43c798 100644 --- a/packages/eslint/src/generators/utils/eslint-file.ts +++ b/packages/eslint/src/generators/utils/eslint-file.ts @@ -14,9 +14,10 @@ import { baseEsLintConfigFile, baseEsLintFlatConfigFile, ESLINT_CONFIG_FILENAMES, + legacyBaseEsLintFlatConfigFile, } from '../../utils/config-file'; import { - getRootESLintFlatConfigFilename, + eslintFlatConfigFilenames, useFlatConfig, } from '../../utils/flat-config'; import { getInstalledEslintVersion } from '../../utils/version-utils'; @@ -50,6 +51,12 @@ export function findEslintFile( if (projectRoot === undefined && tree.exists(baseEsLintFlatConfigFile)) { return baseEsLintFlatConfigFile; } + if ( + projectRoot === undefined && + tree.exists(legacyBaseEsLintFlatConfigFile) + ) { + return legacyBaseEsLintFlatConfigFile; + } projectRoot ??= ''; for (const file of ESLINT_CONFIG_FILENAMES) { if (tree.exists(joinPathFragments(projectRoot, file))) { @@ -188,10 +195,18 @@ export function addOverrideToLintConfig( const isBase = options.checkBaseConfig && findEslintFile(tree, root).includes('.base'); if (useFlatConfig(tree)) { - const fileName = joinPathFragments( - root, - isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree) - ); + let fileName: string; + if (isBase) { + fileName = joinPathFragments(root, baseEsLintFlatConfigFile); + } else { + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } + } + const flatOverride = generateFlatOverride(override); let content = tree.read(fileName, 'utf8'); // Check if the provided override using legacy eslintrc properties or plugins, if so we need to add compat @@ -235,7 +250,15 @@ export function updateOverrideInLintConfig( } if (useFlatConfig(tree)) { - fileName ??= joinPathFragments(root, getRootESLintFlatConfigFilename(tree)); + if (!fileName) { + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } + } + let content = tree.read(fileName, 'utf8'); content = replaceOverride(content, root, lookup, update); tree.write(fileName, content); @@ -282,11 +305,18 @@ export function lintConfigHasOverride( !fileName && checkBaseConfig && findEslintFile(tree, root).includes('.base'); + if (isBase) { + fileName = joinPathFragments(root, baseEsLintFlatConfigFile); + } if (useFlatConfig(tree)) { - fileName ??= joinPathFragments( - root, - isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree) - ); + if (!fileName) { + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } + } const content = tree.read(fileName, 'utf8'); return hasOverride(content, lookup); } else { @@ -305,10 +335,13 @@ export function replaceOverridesInLintConfig( overrides: Linter.ConfigOverride[] ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments( - root, - getRootESLintFlatConfigFilename(tree) - ); + let fileName: string; + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } let content = tree.read(fileName, 'utf8'); // Check if any of the provided overrides using legacy eslintrc properties or plugins, if so we need to add compat if (overrides.some(overrideNeedsCompat)) { @@ -341,10 +374,14 @@ export function addExtendsToLintConfig( ): GeneratorCallback { if (useFlatConfig(tree)) { const pluginExtends: ts.SpreadElement[] = []; - const fileName = joinPathFragments( - root, - getRootESLintFlatConfigFilename(tree) - ); + let fileName: string; + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } + let shouldImportEslintCompat = false; // assume eslint version is 9 if not found, as it's what we'd be generating by default const eslintVersion = @@ -445,10 +482,13 @@ export function addPredefinedConfigToFlatLintConfig( if (!useFlatConfig(tree)) throw new Error('Predefined configs can only be used with flat configs'); - const fileName = joinPathFragments( - root, - getRootESLintFlatConfigFilename(tree) - ); + let fileName: string; + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } let content = tree.read(fileName, 'utf8'); content = addImportToFlatConfig(content, moduleName, moduleImportPath); @@ -468,10 +508,14 @@ export function addPluginsToLintConfig( ) { const plugins = Array.isArray(plugin) ? plugin : [plugin]; if (useFlatConfig(tree)) { - const fileName = joinPathFragments( - root, - getRootESLintFlatConfigFilename(tree) - ); + let fileName: string; + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } + let content = tree.read(fileName, 'utf8'); const mappedPlugins: { name: string; varName: string; imp: string }[] = []; plugins.forEach((name) => { @@ -499,10 +543,14 @@ export function addIgnoresToLintConfig( ignorePatterns: string[] ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments( - root, - getRootESLintFlatConfigFilename(tree) - ); + let fileName: string; + for (const f of eslintFlatConfigFilenames) { + if (tree.exists(joinPathFragments(root, f))) { + fileName = joinPathFragments(root, f); + break; + } + } + const block = generateAst({ ignores: ignorePatterns.map((path) => mapFilePath(path)), }); diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts index 3d17c64a3a..7726ffbfaf 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts @@ -124,6 +124,12 @@ describe('ast-utils', () => { ] }).map(config => ({ ...config, + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.cts", + "**/*.mts" + ], rules: { ...config.rules } @@ -182,7 +188,7 @@ describe('ast-utils', () => { describe('addBlockToFlatConfigExport', () => { it('should inject block to the end of the file', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -204,7 +210,7 @@ describe('ast-utils', () => { }) ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); + "const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -228,7 +234,7 @@ describe('ast-utils', () => { }); it('should inject spread to the beginning of the file', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -246,7 +252,7 @@ describe('ast-utils', () => { { insertAtTheEnd: false } ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); + "const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...config, @@ -266,7 +272,7 @@ describe('ast-utils', () => { describe('addImportToFlatConfig', () => { it('should inject import if not found', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -285,7 +291,7 @@ describe('ast-utils', () => { ); expect(result).toMatchInlineSnapshot(` "const varName = require("@myorg/awesome-config"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -302,7 +308,7 @@ describe('ast-utils', () => { it('should update import if already found', () => { const content = `const { varName } = require("@myorg/awesome-config"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -321,7 +327,7 @@ describe('ast-utils', () => { ); expect(result).toMatchInlineSnapshot(` "const { varName, otherName, someName } = require("@myorg/awesome-config"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -338,7 +344,7 @@ describe('ast-utils', () => { it('should not inject import if already exists', () => { const content = `const { varName, otherName } = require("@myorg/awesome-config"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -360,7 +366,7 @@ describe('ast-utils', () => { it('should not update import if already exists', () => { const content = `const varName = require("@myorg/awesome-config"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -409,7 +415,7 @@ describe('ast-utils', () => { describe('addCompatToFlatConfig', () => { it('should add compat to config', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...baseConfig, { @@ -425,7 +431,7 @@ describe('ast-utils', () => { expect(result).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -446,7 +452,7 @@ describe('ast-utils', () => { }); it('should add only partially compat to config if parts exist', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); const js = require("@eslint/js"); module.exports = [ ...baseConfig, @@ -462,7 +468,7 @@ describe('ast-utils', () => { const result = addFlatCompatToFlatConfig(content); expect(result).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const js = require("@eslint/js"); const compat = new FlatCompat({ @@ -485,7 +491,7 @@ describe('ast-utils', () => { it('should not add compat to config if exist', () => { const content = `const FlatCompat = require("@eslint/eslintrc"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const js = require("@eslint/js"); const compat = new FlatCompat({ @@ -512,7 +518,7 @@ describe('ast-utils', () => { describe('removeOverridesFromLintConfig', () => { it('should remove all rules from config', () => { const content = `const FlatCompat = require("@eslint/eslintrc"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const js = require("@eslint/js"); const compat = new FlatCompat({ @@ -552,7 +558,7 @@ describe('ast-utils', () => { const result = removeOverridesFromLintConfig(content); expect(result).toMatchInlineSnapshot(` "const FlatCompat = require("@eslint/eslintrc"); - const baseConfig = require("../../eslint.config.js"); + const baseConfig = require("../../eslint.config.cjs"); const js = require("@eslint/js"); const compat = new FlatCompat({ @@ -568,7 +574,7 @@ describe('ast-utils', () => { }); it('should remove all rules from starting with first', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ { @@ -599,7 +605,7 @@ describe('ast-utils', () => { ];`; const result = removeOverridesFromLintConfig(content); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); + "const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ];" @@ -609,7 +615,7 @@ describe('ast-utils', () => { describe('replaceOverride', () => { it('should find and replace rules in override', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ { @@ -651,7 +657,7 @@ module.exports = [ }) ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); + "const baseConfig = require("../../eslint.config.cjs"); module.exports = [ { @@ -686,7 +692,7 @@ module.exports = [ }); it('should append rules in override', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ { @@ -722,7 +728,7 @@ module.exports = [ }) ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); + "const baseConfig = require("../../eslint.config.cjs"); module.exports = [ { @@ -749,7 +755,7 @@ module.exports = [ }); it('should work for compat overrides', () => { - const content = `const baseConfig = require("../../eslint.config.js"); + const content = `const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ @@ -777,7 +783,7 @@ module.exports = [ }) ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); + "const baseConfig = require("../../eslint.config.cjs"); module.exports = [ ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts index 5cb81e13a3..80f52c860e 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts @@ -1002,12 +1002,40 @@ export function generateFlatOverride( } // At this point we are applying the flat config compat tooling to the override - const { excludedFiles, parser, parserOptions, rules, files, ...rest } = + let { excludedFiles, parser, parserOptions, rules, files, ...rest } = override; const objectLiteralElements: ts.ObjectLiteralElementLike[] = [ ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')), ]; + + // If converting the JS rule, then we need to match ESLint default and also include .cjs and .mjs files. + if ( + (Array.isArray(rest.extends) && + rest.extends.includes('plugin:@nx/javascript')) || + rest.extends === 'plugin:@nx/javascript' + ) { + const newFiles = new Set(files); + newFiles.add('**/*.js'); + newFiles.add('**/*.jsx'); + newFiles.add('**/*.cjs'); + newFiles.add('**/*.mjs'); + files = Array.from(newFiles); + } + // If converting the TS rule, then we need to match ESLint default and also include .cts and .mts files. + if ( + (Array.isArray(rest.extends) && + rest.extends.includes('plugin:@nx/typescript')) || + rest.extends === 'plugin:@nx/typescript' + ) { + const newFiles = new Set(files); + newFiles.add('**/*.ts'); + newFiles.add('**/*.tsx'); + newFiles.add('**/*.cts'); + newFiles.add('**/*.mts'); + files = Array.from(newFiles); + } + addTSObjectProperty(objectLiteralElements, 'files', files); addTSObjectProperty(objectLiteralElements, 'excludedFiles', excludedFiles); diff --git a/packages/eslint/src/migrations/update-20-3-0/add-file-extensions-to-overrides.spec.ts b/packages/eslint/src/migrations/update-20-3-0/add-file-extensions-to-overrides.spec.ts new file mode 100644 index 0000000000..c9dec8aae4 --- /dev/null +++ b/packages/eslint/src/migrations/update-20-3-0/add-file-extensions-to-overrides.spec.ts @@ -0,0 +1,187 @@ +import { type Tree } from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import migration from './add-file-extensions-to-overrides'; + +describe('add-file-extensions-to-overrides', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add .cjs, .mjs, .cts, .mts file extensions to overrides converted using convert-to-flat-config', async () => { + tree.write( + 'eslint.config.js', + `const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...compat + .config({ + extends: ['plugin:@nx/typescript'], + }) + .map((config) => ({ + ...config, + files: ['**/*.ts', '**/*.tsx'], + rules: { + ...config.rules, + }, + })), + ...compat + .config({ + extends: ['plugin:@nx/javascript'], + }) + .map((config) => ({ + ...config, + files: ['**/*.js', '**/*.jsx'], + rules: { + ...config.rules, + }, + })), +];` + ); + + await migration(tree); + + const updated = tree.read('eslint.config.js', 'utf-8'); + expect(updated).toMatchInlineSnapshot(` + "const { FlatCompat } = require('@eslint/eslintrc'); + const js = require('@eslint/js'); + const nxEslintPlugin = require('@nx/eslint-plugin'); + + const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...compat + .config({ + extends: ['plugin:@nx/typescript'], + }) + .map((config) => ({ + ...config, + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], + rules: { + ...config.rules, + }, + })), + ...compat + .config({ + extends: ['plugin:@nx/javascript'], + }) + .map((config) => ({ + ...config, + files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], + rules: { + ...config.rules, + }, + })), + ];" + `); + }); + + it('should handle duplicates', async () => { + tree.write( + 'eslint.config.js', + `const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...compat + .config({ + extends: ['plugin:@nx/javascript'], + }) + .map((config) => ({ + ...config, + files: ['**/*.js', '**/*.jsx', '**/*.mjs'], + rules: { + ...config.rules, + }, + })), +];` + ); + + await migration(tree); + + const updated = tree.read('eslint.config.js', 'utf-8'); + expect(updated).toMatchInlineSnapshot(` + "const { FlatCompat } = require('@eslint/eslintrc'); + const js = require('@eslint/js'); + const nxEslintPlugin = require('@nx/eslint-plugin'); + + const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...compat + .config({ + extends: ['plugin:@nx/javascript'], + }) + .map((config) => ({ + ...config, + files: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'], + rules: { + ...config.rules, + }, + })), + ];" + `); + }); + + it('should not update if plugin:@nx/javascript and plugin:@nx/typescript are not used', async () => { + const original = `const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...compat + .config({ + extends: ['plugin:@acme/foo'], + }) + .map((config) => ({ + ...config, + files: ['**/*.ts', '**/*.tsx'], + rules: { + ...config.rules, + }, + })), + ...compat + .config({ + extends: ['plugin:@acme/bar'], + }) + .map((config) => ({ + ...config, + files: ['**/*.js', '**/*.jsx'], + rules: { + ...config.rules, + }, + })), +];`; + tree.write('eslint.config.js', original); + + await migration(tree); + + const updated = tree.read('eslint.config.js', 'utf-8'); + expect(updated).toEqual(original); + }); +}); diff --git a/packages/eslint/src/migrations/update-20-3-0/add-file-extensions-to-overrides.ts b/packages/eslint/src/migrations/update-20-3-0/add-file-extensions-to-overrides.ts new file mode 100644 index 0000000000..d2bd661cd8 --- /dev/null +++ b/packages/eslint/src/migrations/update-20-3-0/add-file-extensions-to-overrides.ts @@ -0,0 +1,97 @@ +import { type Tree } from '@nx/devkit'; +import * as ts from 'typescript'; +import { findNodes, replaceChange } from '@nx/js'; + +export default async function (tree: Tree): Promise { + let rootConfig: string; + + // NOTE: we don't support generating ESM base config currently so they are not handled. + for (const candidate of ['eslint.config.js', 'eslint.config.cjs']) { + if (tree.exists(candidate)) { + rootConfig = candidate; + break; + } + } + + if (!rootConfig) return; + + updateOverrideFileExtensions( + tree, + rootConfig, + 'plugin:@nx/typescript', + [`'**/*.ts'`, `'**/*.tsx'`], + [`'**/*.cts'`, `'**/*.mts'`] + ); + + updateOverrideFileExtensions( + tree, + rootConfig, + 'plugin:@nx/javascript', + [`'**/*.js'`, `'**/*.jsx'`], + [`'**/*.cjs'`, `'**/*.mjs'`] + ); +} + +function updateOverrideFileExtensions( + tree: Tree, + configFile: string, + plugin: string, + matchingExts: string[], + newExts: string[] +): void { + const content = tree.read(configFile, 'utf-8'); + const source = ts.createSourceFile( + '', + content, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.JS + ); + let compatNode: ts.SpreadElement; + + const spreadElementNodes = findNodes( + source, + ts.SyntaxKind.SpreadElement + ) as ts.SpreadElement[]; + for (const a of spreadElementNodes) { + const assignmentNodes = findNodes( + a, + ts.SyntaxKind.PropertyAssignment + ) as ts.PropertyAssignment[]; + if (assignmentNodes.length === 0) continue; + for (const b of assignmentNodes) { + if ( + b.name.getText() === 'extends' && + b.initializer.getText().includes(plugin) + ) { + compatNode = a; + break; + } + } + } + + if (compatNode) { + const arrayNodes = findNodes( + compatNode, + ts.SyntaxKind.ArrayLiteralExpression + ) as ts.ArrayLiteralExpression[]; + for (const a of arrayNodes) { + if ( + matchingExts.every((ext) => a.elements.some((e) => e.getText() === ext)) + ) { + const exts = new Set(a.elements.map((e) => e.getText())); + for (const ext of newExts) { + exts.add(ext); + } + replaceChange( + tree, + source, + configFile, + a.getStart(a.getSourceFile()), + `[${Array.from(exts).join(', ')}]`, + a.getText() + ).getText(); + } + } + } +} diff --git a/packages/eslint/src/plugins/plugin.spec.ts b/packages/eslint/src/plugins/plugin.spec.ts index f392ebcca6..7470d906a2 100644 --- a/packages/eslint/src/plugins/plugin.spec.ts +++ b/packages/eslint/src/plugins/plugin.spec.ts @@ -73,13 +73,13 @@ describe('@nx/eslint/plugin', () => { // TODO(leo): dynamic import of the flat config fails with jest: // "TypeError: A dynamic import callback was invoked without --experimental-vm-modules" - // mocking the "eslint.config.js" file import is not working, figure out if there's a way + // mocking the "eslint.config.cjs" file import is not working, figure out if there's a way it.skip('should not create a node for a root level eslint config when accompanied by a project.json, if no src directory is present', async () => { createFiles({ - 'eslint.config.js': `module.exports = {};`, + 'eslint.config.cjs': `module.exports = {};`, 'project.json': `{}`, }); - // NOTE: It should set ESLINT_USE_FLAT_CONFIG to true because of the use of eslint.config.js + // NOTE: It should set ESLINT_USE_FLAT_CONFIG to true because of the use of eslint.config.cjs expect( await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' }) ).toMatchInlineSnapshot(` diff --git a/packages/eslint/src/utils/config-file.ts b/packages/eslint/src/utils/config-file.ts index fc1636f3c0..1394f7de2e 100644 --- a/packages/eslint/src/utils/config-file.ts +++ b/packages/eslint/src/utils/config-file.ts @@ -19,7 +19,9 @@ export const ESLINT_CONFIG_FILENAMES = [ ]; export const baseEsLintConfigFile = '.eslintrc.base.json'; -export const baseEsLintFlatConfigFile = 'eslint.base.config.js'; +export const baseEsLintFlatConfigFile = 'eslint.base.config.cjs'; +// Make sure we can handle previous file extension as well for migrations or custom generators. +export const legacyBaseEsLintFlatConfigFile = 'eslint.base.config.js'; export function isFlatConfig(configFilePath: string): boolean { const configFileName = basename(configFilePath); diff --git a/packages/eslint/src/utils/flat-config.ts b/packages/eslint/src/utils/flat-config.ts index 92f3ae48e4..b28234147b 100644 --- a/packages/eslint/src/utils/flat-config.ts +++ b/packages/eslint/src/utils/flat-config.ts @@ -3,8 +3,8 @@ import { gte } from 'semver'; // todo: add support for eslint.config.mjs, export const eslintFlatConfigFilenames = [ - 'eslint.config.js', 'eslint.config.cjs', + 'eslint.config.js', ]; export function getRootESLintFlatConfigFilename(tree: Tree): string { diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index 7af27a679c..b3bdb5a6bb 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -624,7 +624,7 @@ describe('app', () => { describe('--linter', () => { describe('default (eslint)', () => { it('should add flat config as needed', async () => { - tree.write('eslint.config.js', ''); + tree.write('eslint.config.cjs', ''); const name = uniq(); await applicationGenerator(tree, { @@ -632,12 +632,12 @@ describe('app', () => { style: 'css', }); - expect(tree.read(`${name}/eslint.config.js`, 'utf-8')) + expect(tree.read(`${name}/eslint.config.cjs`, 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require('@eslint/eslintrc'); const js = require('@eslint/js'); const nx = require('@nx/eslint-plugin'); - const baseConfig = require('../eslint.config.js'); + const baseConfig = require('../eslint.config.cjs'); const compat = new FlatCompat({ baseDirectory: __dirname, diff --git a/packages/next/src/generators/application/lib/add-linting.spec.ts b/packages/next/src/generators/application/lib/add-linting.spec.ts index 37782d4096..b7f1ace6c4 100644 --- a/packages/next/src/generators/application/lib/add-linting.spec.ts +++ b/packages/next/src/generators/application/lib/add-linting.spec.ts @@ -96,16 +96,16 @@ describe('updateEslint', () => { }); it('should update the flat config', async () => { - tree.write('eslint.config.js', `module.exports = []`); + tree.write('eslint.config.cjs', `module.exports = []`); await addLinting(tree, schema); - expect(tree.read(`${schema.appProjectRoot}/eslint.config.js`, 'utf-8')) + expect(tree.read(`${schema.appProjectRoot}/eslint.config.cjs`, 'utf-8')) .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); const nx = require("@nx/eslint-plugin"); - const baseConfig = require("../eslint.config.js"); + const baseConfig = require("../eslint.config.cjs"); const compat = new FlatCompat({ baseDirectory: __dirname, diff --git a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap index f080ce37ee..78d84c22e2 100644 --- a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap @@ -35,7 +35,7 @@ exports[`app generated files content - as-provided - my-app general application exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (flat config) 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); const js = require('@eslint/js'); -const baseConfig = require('../eslint.config.js'); +const baseConfig = require('../eslint.config.cjs'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -394,7 +394,7 @@ exports[`app generated files content - as-provided - myApp general application s exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (flat config) 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); const js = require('@eslint/js'); -const baseConfig = require('../eslint.config.js'); +const baseConfig = require('../eslint.config.cjs'); const compat = new FlatCompat({ baseDirectory: __dirname, diff --git a/packages/nuxt/src/generators/application/application.spec.ts b/packages/nuxt/src/generators/application/application.spec.ts index 4e62d440f4..a95f33cd0a 100644 --- a/packages/nuxt/src/generators/application/application.spec.ts +++ b/packages/nuxt/src/generators/application/application.spec.ts @@ -66,7 +66,7 @@ describe('app', () => { }); it('should configure eslint correctly (flat config)', async () => { - tree.write('eslint.config.js', ''); + tree.write('eslint.config.cjs', ''); await applicationGenerator(tree, { directory: name, @@ -74,7 +74,7 @@ describe('app', () => { }); expect( - tree.read(`${name}/eslint.config.js`, 'utf-8') + tree.read(`${name}/eslint.config.cjs`, 'utf-8') ).toMatchSnapshot(); }); diff --git a/packages/nx/src/command-line/init/implementation/angular/standalone-workspace.ts b/packages/nx/src/command-line/init/implementation/angular/standalone-workspace.ts index 9b739048c5..5204cd6954 100644 --- a/packages/nx/src/command-line/init/implementation/angular/standalone-workspace.ts +++ b/packages/nx/src/command-line/init/implementation/angular/standalone-workspace.ts @@ -91,7 +91,7 @@ function createNxJson( ].filter(Boolean) : []), ...(eslintProjectConfigFile - ? ['!{projectRoot}/.eslintrc.json', '!{projectRoot}/eslint.config.js'] + ? ['!{projectRoot}/.eslintrc.json', '!{projectRoot}/eslint.config.cjs'] : []), ].filter(Boolean), }; @@ -124,8 +124,8 @@ function createNxJson( if (fileExists(join(repoRoot, '.eslintrc.json'))) { inputs.push('{workspaceRoot}/.eslintrc.json'); } - if (fileExists(join(repoRoot, 'eslint.config.js'))) { - inputs.push('{workspaceRoot}/eslint.config.js'); + if (fileExists(join(repoRoot, 'eslint.config.cjs'))) { + inputs.push('{workspaceRoot}/eslint.config.cjs'); } nxJson.targetDefaults.lint = { ...nxJson.targetDefaults.lint, @@ -230,7 +230,9 @@ function projectHasEslintConfig( ): boolean { return ( fileExists(join(project.root, '.eslintrc.json')) || - fileExists(join(project.root, 'eslint.config.js')) + fileExists(join(project.root, 'eslint.config.js')) || + fileExists(join(project.root, 'eslint.config.mjs')) || + fileExists(join(project.root, 'eslint.config.cjs')) ); } diff --git a/packages/nx/src/command-line/release/config/config.spec.ts b/packages/nx/src/command-line/release/config/config.spec.ts index 4216e0349c..76e3b3bfa9 100644 --- a/packages/nx/src/command-line/release/config/config.spec.ts +++ b/packages/nx/src/command-line/release/config/config.spec.ts @@ -10923,7 +10923,7 @@ describe('createNxReleaseConfig()', () => { 'group-1': { projects: 'nx', versionPlans: { - ignorePatternsForPlanCheck: ['**/eslint.config.js'], + ignorePatternsForPlanCheck: ['**/eslint.config.cjs'], }, }, 'group-2': { @@ -11076,7 +11076,7 @@ describe('createNxReleaseConfig()', () => { }, "versionPlans": { "ignorePatternsForPlanCheck": [ - "**/eslint.config.js", + "**/eslint.config.cjs", ], }, }, diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index a2dd0a4aa8..02569645a8 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -309,52 +309,6 @@ export default {...nxPreset}; tasks.push(await addE2E(tree, options)); - // If the project package.json uses type module, and the project uses flat eslint config, we need to make sure the eslint config uses an explicit .cjs extension - // TODO: This could be re-evaluated once we support ESM in eslint configs - if ( - tree.exists(joinPathFragments(options.projectRoot, 'package.json')) && - tree.exists(joinPathFragments(options.projectRoot, 'eslint.config.js')) - ) { - const pkgJson = readJson( - tree, - joinPathFragments(options.projectRoot, 'package.json') - ); - if (pkgJson.type === 'module') { - tree.rename( - joinPathFragments(options.projectRoot, 'eslint.config.js'), - joinPathFragments(options.projectRoot, 'eslint.config.cjs') - ); - visitNotIgnoredFiles(tree, options.projectRoot, (file) => { - if (file.endsWith('eslint.config.js')) { - // Replace any extends on the eslint config to use the .cjs extension - const content = tree.read(file).toString(); - if (content.includes('eslint.config')) { - tree.write( - file, - content - .replace(/eslint\.config'/g, `eslint.config.cjs'`) - .replace(/eslint\.config"/g, `eslint.config.cjs"`) - .replace(/eslint\.config\.js/g, `eslint.config.cjs`) - ); - } - - // If there is no sibling package.json with type commonjs, we need to rename the .js files to .cjs - const siblingPackageJsonPath = joinPathFragments( - dirname(file), - 'package.json' - ); - if (tree.exists(siblingPackageJsonPath)) { - const siblingPkgJson = readJson(tree, siblingPackageJsonPath); - if (siblingPkgJson.type === 'module') { - return; - } - } - tree.rename(file, file.replace('.js', '.cjs')); - } - }); - } - } - addViteTempFilesToGitIgnore(tree); if (!options.skipFormat) { await formatFiles(tree); diff --git a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap index 0981da1f57..89fd88a7b0 100644 --- a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap @@ -279,7 +279,7 @@ exports[`library should ignore test files in tsconfig.lib.json 1`] = ` exports[`library should support eslint flat config 1`] = ` "const vue = require('eslint-plugin-vue'); -const baseConfig = require('../eslint.config.js'); +const baseConfig = require('../eslint.config.cjs'); module.exports = [ ...baseConfig, diff --git a/packages/vue/src/generators/library/library.spec.ts b/packages/vue/src/generators/library/library.spec.ts index 75e883d9fd..73ee4dea02 100644 --- a/packages/vue/src/generators/library/library.spec.ts +++ b/packages/vue/src/generators/library/library.spec.ts @@ -151,7 +151,7 @@ describe('library', () => { it('should support eslint flat config', async () => { tree.write( - 'eslint.config.js', + 'eslint.config.cjs', `const { FlatCompat } = require('@eslint/eslintrc'); const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); @@ -208,10 +208,10 @@ module.exports = [ await libraryGenerator(tree, defaultSchema); - const eslintJson = tree.read('my-lib/eslint.config.js', 'utf-8'); + const eslintJson = tree.read('my-lib/eslint.config.cjs', 'utf-8'); expect(eslintJson).toMatchSnapshot(); // assert **/*.vue was added to override in base eslint config - const eslintBaseJson = tree.read('eslint.config.js', 'utf-8'); + const eslintBaseJson = tree.read('eslint.config.cjs', 'utf-8'); expect(eslintBaseJson).toContain( `files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],` ); diff --git a/packages/workspace/src/generators/move/lib/__snapshots__/create-project-configuration-in-new-destination.spec.ts.snap b/packages/workspace/src/generators/move/lib/__snapshots__/create-project-configuration-in-new-destination.spec.ts.snap index 658a6e7771..7d684a3df0 100644 --- a/packages/workspace/src/generators/move/lib/__snapshots__/create-project-configuration-in-new-destination.spec.ts.snap +++ b/packages/workspace/src/generators/move/lib/__snapshots__/create-project-configuration-in-new-destination.spec.ts.snap @@ -140,7 +140,7 @@ exports[`moveProjectConfiguration should rename the project correctly except for "default", "{workspaceRoot}/.eslintrc.json", "{workspaceRoot}/.eslintignore", - "{workspaceRoot}/eslint.config.js", + "{workspaceRoot}/eslint.config.cjs", ], "options": {}, "outputs": [ diff --git a/packages/workspace/src/generators/move/lib/create-project-configuration-in-new-destination.spec.ts b/packages/workspace/src/generators/move/lib/create-project-configuration-in-new-destination.spec.ts index 6132e29fe9..0cd0ac2c0a 100644 --- a/packages/workspace/src/generators/move/lib/create-project-configuration-in-new-destination.spec.ts +++ b/packages/workspace/src/generators/move/lib/create-project-configuration-in-new-destination.spec.ts @@ -199,7 +199,7 @@ describe('moveProjectConfiguration', () => { 'default', '{workspaceRoot}/.eslintrc.json', '{workspaceRoot}/.eslintignore', - '{workspaceRoot}/eslint.config.js', + '{workspaceRoot}/eslint.config.cjs', ], executor: '@nx/eslint:lint', outputs: ['{options.outputFile}'], diff --git a/packages/workspace/src/generators/move/lib/move-project-files.ts b/packages/workspace/src/generators/move/lib/move-project-files.ts index d57c8c1fdd..9446f306bc 100644 --- a/packages/workspace/src/generators/move/lib/move-project-files.ts +++ b/packages/workspace/src/generators/move/lib/move-project-files.ts @@ -22,6 +22,7 @@ export function moveProjectFiles( '.babelrc', '.eslintrc.json', 'eslint.config.js', + 'eslint.config.cjs', /^jest\.config\.((app|lib)\.)?[jt]s$/, 'vite.config.ts', /^webpack.*\.js$/, diff --git a/packages/workspace/src/generators/move/lib/update-eslint-config.spec.ts b/packages/workspace/src/generators/move/lib/update-eslint-config.spec.ts index 0a0ff080b8..beafb32fd6 100644 --- a/packages/workspace/src/generators/move/lib/update-eslint-config.spec.ts +++ b/packages/workspace/src/generators/move/lib/update-eslint-config.spec.ts @@ -243,7 +243,7 @@ describe('updateEslint (flat config)', () => { tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree.delete('.eslintrc.json'); - tree.write('eslint.config.js', `module.exports = [];`); + tree.write('eslint.config.cjs', `module.exports = [];`); }); it('should handle config not existing', async () => { @@ -267,16 +267,16 @@ describe('updateEslint (flat config)', () => { convertToFlat(tree, 'my-lib'); // This step is usually handled elsewhere tree.rename( - 'my-lib/eslint.config.js', - 'shared/my-destination/eslint.config.js' + 'my-lib/eslint.config.cjs', + 'shared/my-destination/eslint.config.cjs' ); const projectConfig = readProjectConfiguration(tree, 'my-lib'); updateEslintConfig(tree, schema, projectConfig); expect( - tree.read('shared/my-destination/eslint.config.js', 'utf-8') - ).toEqual(expect.stringContaining(`require('../../eslint.config.js')`)); + tree.read('shared/my-destination/eslint.config.cjs', 'utf-8') + ).toEqual(expect.stringContaining(`require('../../eslint.config.cjs')`)); }); it('should update config extends path when project is moved from subdirectory', async () => { @@ -287,7 +287,7 @@ describe('updateEslint (flat config)', () => { }); convertToFlat(tree, 'api/test'); // This step is usually handled elsewhere - tree.rename('api/test/eslint.config.js', 'test/eslint.config.js'); + tree.rename('api/test/eslint.config.cjs', 'test/eslint.config.cjs'); const projectConfig = readProjectConfiguration(tree, 'api-test'); @@ -302,8 +302,8 @@ describe('updateEslint (flat config)', () => { updateEslintConfig(tree, newSchema, projectConfig); - expect(tree.read('test/eslint.config.js', 'utf-8')).toEqual( - expect.stringContaining(`require('../eslint.config.js')`) + expect(tree.read('test/eslint.config.cjs', 'utf-8')).toEqual( + expect.stringContaining(`require('../eslint.config.cjs')`) ); }); @@ -316,15 +316,15 @@ describe('updateEslint (flat config)', () => { convertToFlat(tree, 'my-lib', { hasParser: true }); // This step is usually handled elsewhere tree.rename( - 'my-lib/eslint.config.js', - 'shared/my-destination/eslint.config.js' + 'my-lib/eslint.config.cjs', + 'shared/my-destination/eslint.config.cjs' ); const projectConfig = readProjectConfiguration(tree, 'my-lib'); updateEslintConfig(tree, schema, projectConfig); expect( - tree.read('shared/my-destination/eslint.config.js', 'utf-8') + tree.read('shared/my-destination/eslint.config.cjs', 'utf-8') ).toEqual( expect.stringContaining( `project: ["shared/my-destination/tsconfig.*?.json"]` @@ -346,15 +346,15 @@ describe('updateEslint (flat config)', () => { }); // This step is usually handled elsewhere tree.rename( - 'my-lib/eslint.config.js', - 'shared/my-destination/eslint.config.js' + 'my-lib/eslint.config.cjs', + 'shared/my-destination/eslint.config.cjs' ); const projectConfig = readProjectConfiguration(tree, 'my-lib'); updateEslintConfig(tree, schema, projectConfig); expect( - tree.read('shared/my-destination/eslint.config.js', 'utf-8') + tree.read('shared/my-destination/eslint.config.cjs', 'utf-8') ).toEqual( expect.stringContaining( `project: ["shared/my-destination/tsconfig.*?.json", "shared/my-destination/${storybookProject}"]` @@ -372,15 +372,15 @@ describe('updateEslint (flat config)', () => { convertToFlat(tree, 'my-lib', { hasParser: true, isString: true }); // This step is usually handled elsewhere tree.rename( - 'my-lib/eslint.config.js', - 'shared/my-destination/eslint.config.js' + 'my-lib/eslint.config.cjs', + 'shared/my-destination/eslint.config.cjs' ); const projectConfig = readProjectConfiguration(tree, 'my-lib'); updateEslintConfig(tree, schema, projectConfig); expect( - tree.read('shared/my-destination/eslint.config.js', 'utf-8') + tree.read('shared/my-destination/eslint.config.cjs', 'utf-8') ).toEqual( expect.stringContaining( `project: "shared/my-destination/tsconfig.*?.json"` @@ -417,9 +417,9 @@ function convertToFlat( } tree.write( - joinPathFragments(path, 'eslint.config.js'), + joinPathFragments(path, 'eslint.config.cjs'), `const { FlatCompat } = require("@eslint/eslintrc"); - const baseConfig = require("${offset}eslint.config.js"); + const baseConfig = require("${offset}eslint.config.cjs"); const js = require("@eslint/js"); const compat = new FlatCompat({ baseDirectory: __dirname, diff --git a/packages/workspace/src/generators/move/lib/update-eslint-config.ts b/packages/workspace/src/generators/move/lib/update-eslint-config.ts index 4dd03c8c0e..7575c4ded7 100644 --- a/packages/workspace/src/generators/move/lib/update-eslint-config.ts +++ b/packages/workspace/src/generators/move/lib/update-eslint-config.ts @@ -15,8 +15,10 @@ export function updateEslintConfig( if ( !tree.exists('.eslintrc.json') && !tree.exists('eslint.config.js') && + !tree.exists('eslint.config.cjs') && !tree.exists('.eslintrc.base.json') && - !tree.exists('eslint.base.config.js') + !tree.exists('eslint.base.config.js') && + !tree.exists('eslint.base.config.cjs') ) { return; } diff --git a/packages/workspace/src/generators/move/lib/update-project-root-files.ts b/packages/workspace/src/generators/move/lib/update-project-root-files.ts index c516488cc0..b7bcc7cd41 100644 --- a/packages/workspace/src/generators/move/lib/update-project-root-files.ts +++ b/packages/workspace/src/generators/move/lib/update-project-root-files.ts @@ -51,7 +51,11 @@ export function updateFilesForRootProjects( if (!allowedExt.includes(ext)) { continue; } - if (file === '.eslintrc.json' || file === 'eslint.config.js') { + if ( + file === '.eslintrc.json' || + file === 'eslint.config.js' || + file === 'eslint.config.cjs' + ) { continue; } @@ -104,7 +108,11 @@ export function updateFilesForNonRootProjects( if (!allowedExt.includes(ext)) { continue; } - if (file === '.eslintrc.json' || file === 'eslint.config.js') { + if ( + file === '.eslintrc.json' || + file === 'eslint.config.cjs' || + file === 'eslint.config.js' + ) { continue; }