From 81fe13250ae1a4619e01e5d5087c3ac72f9bc25f Mon Sep 17 00:00:00 2001 From: Ben Snyder Date: Mon, 8 Jul 2024 12:51:13 -0400 Subject: [PATCH] feat(linter): support `eslint.config.cjs` and `*.cjs` extension with flat config (#26637) --- .../lint/utility/eslint-utils.spec.ts | 4 ++-- .../executors/lint/utility/eslint-utils.ts | 3 ++- .../src/generators/utils/eslint-file.ts | 19 ++++++++++------- packages/eslint/src/utils/config-file.ts | 5 +++-- packages/eslint/src/utils/flat-config.ts | 21 ++++++++++++++++++- 5 files changed, 38 insertions(+), 14 deletions(-) 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 fd41069772d09..3dde87789fcdb 100644 --- a/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts +++ b/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts @@ -162,11 +162,11 @@ describe('eslint-utils', () => { }); describe('ESLint Flat Config', () => { - it('should throw if a non eslint.config.js file is used with ESLint Flat Config', async () => { + it('should throw if a non eslint.config.js or eslint.config.cjs file is used with ESLint Flat Config', async () => { await expect( resolveAndInstantiateESLint('./.eslintrc.json', {} as any, true) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"When using the new Flat Config with ESLint, all configs must be named eslint.config.js and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files-new"` + `"When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files"` ); }); diff --git a/packages/eslint/src/executors/lint/utility/eslint-utils.ts b/packages/eslint/src/executors/lint/utility/eslint-utils.ts index 8a658bec4795e..e9e85baf6b34d 100644 --- a/packages/eslint/src/executors/lint/utility/eslint-utils.ts +++ b/packages/eslint/src/executors/lint/utility/eslint-utils.ts @@ -10,7 +10,8 @@ export async function resolveAndInstantiateESLint( ) { if (useFlatConfig && eslintConfigPath && !isFlatConfig(eslintConfigPath)) { throw new Error( - 'When using the new Flat Config with ESLint, all configs must be named eslint.config.js and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files-new' + // todo: add support for eslint.config.mjs, + 'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files' ); } const ESLint = await resolveESLintClass(useFlatConfig); diff --git a/packages/eslint/src/generators/utils/eslint-file.ts b/packages/eslint/src/generators/utils/eslint-file.ts index 90e23965f4837..13226afd8b8b2 100644 --- a/packages/eslint/src/generators/utils/eslint-file.ts +++ b/packages/eslint/src/generators/utils/eslint-file.ts @@ -7,7 +7,10 @@ import { } from '@nx/devkit'; import type { Tree } from '@nx/devkit'; import type { Linter } from 'eslint'; -import { useFlatConfig } from '../../utils/flat-config'; +import { + flatConfigEslintFilename, + useFlatConfig, +} from '../../utils/flat-config'; import { addBlockToFlatConfigExport, addCompatToFlatConfig, @@ -174,7 +177,7 @@ export function addOverrideToLintConfig( if (useFlatConfig(tree)) { const fileName = joinPathFragments( root, - isBase ? baseEsLintFlatConfigFile : 'eslint.config.js' + isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree) ); const flatOverride = generateFlatOverride(override); let content = tree.read(fileName, 'utf8'); @@ -220,7 +223,7 @@ export function updateOverrideInLintConfig( ) => Linter.ConfigOverride ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, 'eslint.config.js'); + const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); let content = tree.read(fileName, 'utf8'); content = replaceOverride(content, root, lookup, update); tree.write(fileName, content); @@ -262,7 +265,7 @@ export function lintConfigHasOverride( if (useFlatConfig(tree)) { const fileName = joinPathFragments( root, - isBase ? baseEsLintFlatConfigFile : 'eslint.config.js' + isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree) ); const content = tree.read(fileName, 'utf8'); return hasOverride(content, lookup); @@ -282,7 +285,7 @@ export function replaceOverridesInLintConfig( overrides: Linter.ConfigOverride[] ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, 'eslint.config.js'); + const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); let content = tree.read(fileName, 'utf8'); // we will be using compat here so we need to make sure it's added if (overrides.some(overrideNeedsCompat)) { @@ -311,7 +314,7 @@ export function addExtendsToLintConfig( ) { const plugins = Array.isArray(plugin) ? plugin : [plugin]; if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, 'eslint.config.js'); + const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); const pluginExtends = generatePluginExtendsElement(plugins); let content = tree.read(fileName, 'utf8'); content = addCompatToFlatConfig(content); @@ -341,7 +344,7 @@ export function addPluginsToLintConfig( ) { const plugins = Array.isArray(plugin) ? plugin : [plugin]; if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, 'eslint.config.js'); + const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); let content = tree.read(fileName, 'utf8'); const mappedPlugins: { name: string; varName: string; imp: string }[] = []; plugins.forEach((name) => { @@ -369,7 +372,7 @@ export function addIgnoresToLintConfig( ignorePatterns: string[] ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, 'eslint.config.js'); + const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); const block = generateAst({ ignores: ignorePatterns.map((path) => mapFilePath(path)), }); diff --git a/packages/eslint/src/utils/config-file.ts b/packages/eslint/src/utils/config-file.ts index 42beb03646eac..a7e4e48c15633 100644 --- a/packages/eslint/src/utils/config-file.ts +++ b/packages/eslint/src/utils/config-file.ts @@ -1,8 +1,8 @@ import { existsSync, statSync } from 'fs'; import { basename, dirname, join, resolve } from 'path'; +import { eslintFlatConfigFilenames } from './flat-config'; -// TODO(leo): add support for eslint.config.mjs and eslint.config.cjs -export const ESLINT_FLAT_CONFIG_FILENAMES = ['eslint.config.js']; +export const ESLINT_FLAT_CONFIG_FILENAMES = eslintFlatConfigFilenames; export const ESLINT_OLD_CONFIG_FILENAMES = [ '.eslintrc', @@ -40,6 +40,7 @@ export function findFlatConfigFile( ESLINT_FLAT_CONFIG_FILENAMES ); if (configFilePath) { + console.log(`Found eslint flat config file at: ${configFilePath}`); return configFilePath; } if (currentDir === workspaceRoot) { diff --git a/packages/eslint/src/utils/flat-config.ts b/packages/eslint/src/utils/flat-config.ts index 2943a4948e39a..0e54ec132ef7d 100644 --- a/packages/eslint/src/utils/flat-config.ts +++ b/packages/eslint/src/utils/flat-config.ts @@ -1,5 +1,24 @@ import { Tree } from '@nx/devkit'; +// todo: add support for eslint.config.mjs, +export const eslintFlatConfigFilenames = [ + 'eslint.config.js', + 'eslint.config.cjs', +]; + +export function flatConfigEslintFilename(tree: Tree): string { + for (const file of eslintFlatConfigFilenames) { + if (tree.exists(file)) { + return file; + } + } + throw new Error('Could not find flat config file'); +} + export function useFlatConfig(tree: Tree): boolean { - return tree.exists('eslint.config.js'); + try { + return !!flatConfigEslintFilename(tree); + } catch { + return false; + } }