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 { joinPathFragments, type ExecutorContext } from '@nx/devkit';
|
||||||
import { ESLint } from 'eslint';
|
import type { ESLint } from 'eslint';
|
||||||
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
import { mkdirSync, writeFileSync } from 'fs';
|
||||||
import { dirname, resolve } from 'path';
|
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 type { Schema } from './schema';
|
||||||
import { resolveAndInstantiateESLint } from './utility/eslint-utils';
|
import { resolveAndInstantiateESLint } from './utility/eslint-utils';
|
||||||
import { interpolate } from 'nx/src/tasks-runner/utils';
|
|
||||||
|
|
||||||
export default async function run(
|
export default async function run(
|
||||||
options: Schema,
|
options: Schema,
|
||||||
@ -37,22 +37,18 @@ export default async function run(
|
|||||||
const { printConfig, errorOnUnmatchedPattern, ...normalizedOptions } =
|
const { printConfig, errorOnUnmatchedPattern, ...normalizedOptions } =
|
||||||
options;
|
options;
|
||||||
|
|
||||||
/**
|
// locate the flat config file if it exists starting from the project root
|
||||||
* Until ESLint v9 is released and the new so called flat config is the default
|
const flatConfigFilePath = findFlatConfigFile(projectRoot, context.root);
|
||||||
* we only want to support it if the user has explicitly opted into it by converting
|
const hasFlatConfig = flatConfigFilePath !== null;
|
||||||
* their root ESLint config to use eslint.config.js
|
|
||||||
*/
|
|
||||||
const hasFlatConfig = existsSync(
|
|
||||||
joinPathFragments(workspaceRoot, 'eslint.config.js')
|
|
||||||
);
|
|
||||||
|
|
||||||
// while standard eslint uses by default closest config to the file, if otherwise not specified,
|
// 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) {
|
if (hasFlatConfig && !normalizedOptions.eslintConfig) {
|
||||||
const eslintConfigPath = joinPathFragments(projectRoot, 'eslint.config.js');
|
normalizedOptions.eslintConfig = posix.relative(
|
||||||
if (existsSync(eslintConfigPath)) {
|
systemRoot,
|
||||||
normalizedOptions.eslintConfig = eslintConfigPath;
|
flatConfigFilePath
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ESLint } from 'eslint';
|
import type { ESLint } from 'eslint';
|
||||||
|
import { isFlatConfig } from '../../../utils/config-file';
|
||||||
import { resolveESLintClass } from '../../../utils/resolve-eslint-class';
|
import { resolveESLintClass } from '../../../utils/resolve-eslint-class';
|
||||||
import type { Schema } from '../schema';
|
import type { Schema } from '../schema';
|
||||||
|
|
||||||
@ -7,11 +8,7 @@ export async function resolveAndInstantiateESLint(
|
|||||||
options: Schema,
|
options: Schema,
|
||||||
useFlatConfig = false
|
useFlatConfig = false
|
||||||
) {
|
) {
|
||||||
if (
|
if (useFlatConfig && eslintConfigPath && !isFlatConfig(eslintConfigPath)) {
|
||||||
useFlatConfig &&
|
|
||||||
eslintConfigPath &&
|
|
||||||
!eslintConfigPath?.endsWith('eslint.config.js')
|
|
||||||
) {
|
|
||||||
throw new Error(
|
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'
|
'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, statSync } from 'fs';
|
||||||
import { existsSync } 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',
|
||||||
'.eslintrc.js',
|
'.eslintrc.js',
|
||||||
'.eslintrc.cjs',
|
'.eslintrc.cjs',
|
||||||
'.eslintrc.yaml',
|
'.eslintrc.yaml',
|
||||||
'.eslintrc.yml',
|
'.eslintrc.yml',
|
||||||
'.eslintrc.json',
|
'.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 baseEsLintConfigFile = '.eslintrc.base.json';
|
||||||
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
||||||
|
|
||||||
export function isFlatConfig(configFilePath: string): boolean {
|
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