fix(linter): check for flat config correctly in @nx/eslint:lint executor (#26350)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #22575
This commit is contained in:
parent
f8239debd0
commit
f4b379f459
@ -1,11 +1,11 @@
|
||||
import { ExecutorContext, joinPathFragments, workspaceRoot } from '@nx/devkit';
|
||||
import { ESLint } from 'eslint';
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
import { joinPathFragments, type ExecutorContext } from '@nx/devkit';
|
||||
import type { ESLint } from 'eslint';
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { interpolate } from 'nx/src/tasks-runner/utils';
|
||||
import { dirname, posix, resolve } from 'path';
|
||||
import { findFlatConfigFile } from '../../utils/config-file';
|
||||
import type { Schema } from './schema';
|
||||
import { resolveAndInstantiateESLint } from './utility/eslint-utils';
|
||||
import { interpolate } from 'nx/src/tasks-runner/utils';
|
||||
|
||||
export default async function run(
|
||||
options: Schema,
|
||||
@ -37,22 +37,18 @@ export default async function run(
|
||||
const { printConfig, errorOnUnmatchedPattern, ...normalizedOptions } =
|
||||
options;
|
||||
|
||||
/**
|
||||
* Until ESLint v9 is released and the new so called flat config is the default
|
||||
* we only want to support it if the user has explicitly opted into it by converting
|
||||
* their root ESLint config to use eslint.config.js
|
||||
*/
|
||||
const hasFlatConfig = existsSync(
|
||||
joinPathFragments(workspaceRoot, 'eslint.config.js')
|
||||
);
|
||||
// locate the flat config file if it exists starting from the project root
|
||||
const flatConfigFilePath = findFlatConfigFile(projectRoot, context.root);
|
||||
const hasFlatConfig = flatConfigFilePath !== null;
|
||||
|
||||
// while standard eslint uses by default closest config to the file, if otherwise not specified,
|
||||
// the flat config would always use the root config, so we need to explicitly set it to the local one
|
||||
// the flat config would be resolved starting from the cwd, which we changed to the workspace root
|
||||
// so we explicitly set the config path to the flat config file path we previously found
|
||||
if (hasFlatConfig && !normalizedOptions.eslintConfig) {
|
||||
const eslintConfigPath = joinPathFragments(projectRoot, 'eslint.config.js');
|
||||
if (existsSync(eslintConfigPath)) {
|
||||
normalizedOptions.eslintConfig = eslintConfigPath;
|
||||
}
|
||||
normalizedOptions.eslintConfig = posix.relative(
|
||||
systemRoot,
|
||||
flatConfigFilePath
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ESLint } from 'eslint';
|
||||
import { isFlatConfig } from '../../../utils/config-file';
|
||||
import { resolveESLintClass } from '../../../utils/resolve-eslint-class';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
@ -7,11 +8,7 @@ export async function resolveAndInstantiateESLint(
|
||||
options: Schema,
|
||||
useFlatConfig = false
|
||||
) {
|
||||
if (
|
||||
useFlatConfig &&
|
||||
eslintConfigPath &&
|
||||
!eslintConfigPath?.endsWith('eslint.config.js')
|
||||
) {
|
||||
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'
|
||||
);
|
||||
|
||||
@ -1,19 +1,93 @@
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
import { existsSync, statSync } from 'fs';
|
||||
import { basename, dirname, join, resolve } from 'path';
|
||||
|
||||
export const ESLINT_CONFIG_FILENAMES = [
|
||||
// TODO(leo): add support for eslint.config.mjs and eslint.config.cjs
|
||||
export const ESLINT_FLAT_CONFIG_FILENAMES = ['eslint.config.js'];
|
||||
|
||||
export const ESLINT_OLD_CONFIG_FILENAMES = [
|
||||
'.eslintrc',
|
||||
'.eslintrc.js',
|
||||
'.eslintrc.cjs',
|
||||
'.eslintrc.yaml',
|
||||
'.eslintrc.yml',
|
||||
'.eslintrc.json',
|
||||
'eslint.config.js',
|
||||
];
|
||||
|
||||
export const ESLINT_CONFIG_FILENAMES = [
|
||||
...ESLINT_OLD_CONFIG_FILENAMES,
|
||||
...ESLINT_FLAT_CONFIG_FILENAMES,
|
||||
];
|
||||
|
||||
export const baseEsLintConfigFile = '.eslintrc.base.json';
|
||||
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
||||
|
||||
export function isFlatConfig(configFilePath: string): boolean {
|
||||
return configFilePath.endsWith('.config.js');
|
||||
const configFileName = basename(configFilePath);
|
||||
|
||||
return ESLINT_FLAT_CONFIG_FILENAMES.includes(configFileName);
|
||||
}
|
||||
|
||||
// https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-resolution
|
||||
export function findFlatConfigFile(
|
||||
directory: string,
|
||||
workspaceRoot: string
|
||||
): string | null {
|
||||
let currentDir = resolve(workspaceRoot, directory);
|
||||
|
||||
if (currentDir === workspaceRoot) {
|
||||
return getConfigFileInDirectory(currentDir, ESLINT_FLAT_CONFIG_FILENAMES);
|
||||
}
|
||||
|
||||
while (currentDir !== workspaceRoot) {
|
||||
const configFilePath = getConfigFileInDirectory(
|
||||
currentDir,
|
||||
ESLINT_FLAT_CONFIG_FILENAMES
|
||||
);
|
||||
if (configFilePath) {
|
||||
return configFilePath;
|
||||
}
|
||||
currentDir = dirname(currentDir);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findOldConfigFile(
|
||||
filePathOrDirectory: string,
|
||||
workspaceRoot: string
|
||||
): string | null {
|
||||
let currentDir = statSync(filePathOrDirectory).isDirectory()
|
||||
? filePathOrDirectory
|
||||
: dirname(filePathOrDirectory);
|
||||
|
||||
if (currentDir === workspaceRoot) {
|
||||
return getConfigFileInDirectory(currentDir, ESLINT_OLD_CONFIG_FILENAMES);
|
||||
}
|
||||
|
||||
while (currentDir !== workspaceRoot) {
|
||||
const configFilePath = getConfigFileInDirectory(
|
||||
currentDir,
|
||||
ESLINT_OLD_CONFIG_FILENAMES
|
||||
);
|
||||
if (configFilePath) {
|
||||
return configFilePath;
|
||||
}
|
||||
currentDir = dirname(currentDir);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getConfigFileInDirectory(
|
||||
directory: string,
|
||||
candidateFileNames: string[]
|
||||
): string | null {
|
||||
for (const filename of candidateFileNames) {
|
||||
const filePath = join(directory, filename);
|
||||
if (existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user