feat(linter): add create-nodes plugin (#20264)
This commit is contained in:
parent
a395fd3c4e
commit
fe63f856ec
@ -764,6 +764,45 @@ describe('Linter', () => {
|
|||||||
expect(e2eEslint.overrides[0].extends).toBeUndefined();
|
expect(e2eEslint.overrides[0].extends).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Project Config v3', () => {
|
||||||
|
let myapp;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
myapp = uniq('myapp');
|
||||||
|
newProject({
|
||||||
|
name: uniq('eslint'),
|
||||||
|
unsetProjectNameAndRootFormat: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should lint example app', () => {
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/react:app ${myapp} --directory apps/${myapp} --unitTestRunner=none --bundler=vite --e2eTestRunner=cypress --style=css --no-interactive --projectNameAndRootFormat=as-provided`,
|
||||||
|
{ env: { NX_PCV3: 'true' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
let lintResults = runCLI(`lint ${myapp}`);
|
||||||
|
expect(lintResults).toContain(
|
||||||
|
`Successfully ran target lint for project ${myapp}`
|
||||||
|
);
|
||||||
|
lintResults = runCLI(`lint ${myapp}-e2e`);
|
||||||
|
expect(lintResults).toContain(
|
||||||
|
`Successfully ran target lint for project ${myapp}-e2e`
|
||||||
|
);
|
||||||
|
|
||||||
|
const { targets } = readJson(`apps/${myapp}/project.json`);
|
||||||
|
expect(targets.lint).not.toBeDefined();
|
||||||
|
|
||||||
|
const { plugins } = readJson('nx.json');
|
||||||
|
expect(plugins).toContainEqual({
|
||||||
|
plugin: '@nx/eslint/plugin',
|
||||||
|
options: {
|
||||||
|
targetName: 'lint',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -134,7 +134,7 @@ function removeConfigurationDefinedByPlugin<T>(
|
|||||||
for (const [optionName, optionValue] of Object.entries(
|
for (const [optionName, optionValue] of Object.entries(
|
||||||
targetFromProjectConfig.options ?? {}
|
targetFromProjectConfig.options ?? {}
|
||||||
)) {
|
)) {
|
||||||
if (targetFromCreateNodes.options[optionName] === optionValue) {
|
if (equals(targetFromCreateNodes.options[optionName], optionValue)) {
|
||||||
delete targetFromProjectConfig.options[optionName];
|
delete targetFromProjectConfig.options[optionName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,6 +167,16 @@ function removeConfigurationDefinedByPlugin<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function equals<T extends unknown>(a: T, b: T) {
|
||||||
|
if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
|
return a.length === b.length && a.every((v, i) => v === b[i]);
|
||||||
|
}
|
||||||
|
if (typeof a === 'object' && typeof b === 'object') {
|
||||||
|
return hashObject(a) === hashObject(b);
|
||||||
|
}
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
|
|
||||||
function shouldRemoveArrayProperty(
|
function shouldRemoveArrayProperty(
|
||||||
arrayValuesFromProjectConfiguration: (object | string)[],
|
arrayValuesFromProjectConfiguration: (object | string)[],
|
||||||
arrayValuesFromCreateNodes: (object | string)[]
|
arrayValuesFromCreateNodes: (object | string)[]
|
||||||
|
|||||||
@ -27,16 +27,16 @@
|
|||||||
"requirements": {},
|
"requirements": {},
|
||||||
"migrations": "./migrations.json"
|
"migrations": "./migrations.json"
|
||||||
},
|
},
|
||||||
"executors": "./executors.json",
|
|
||||||
"generators": "./generators.json",
|
"generators": "./generators.json",
|
||||||
|
"executors": "./executors.json",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^8.0.0",
|
"eslint": "^8.0.0",
|
||||||
"js-yaml": "4.1.0"
|
"js-yaml": "4.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0",
|
|
||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.2.2"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
|
|||||||
1
packages/eslint/plugin.ts
Normal file
1
packages/eslint/plugin.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { createNodes, EslintPluginOptions } from './src/plugins/plugin';
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`@nx/eslint:init --linter eslint should generate the global eslint config 1`] = `
|
exports[`@nx/eslint:init should generate the global eslint config 1`] = `
|
||||||
"{
|
"{
|
||||||
"root": true,
|
"root": true,
|
||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Linter } from '../utils/linter';
|
import { Linter } from '../utils/linter';
|
||||||
import { readJson, Tree } from '@nx/devkit';
|
import { NxJsonConfiguration, readJson, Tree, updateJson } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { lintInitGenerator } from './init';
|
import { lintInitGenerator } from './init';
|
||||||
|
|
||||||
@ -10,45 +10,91 @@ describe('@nx/eslint:init', () => {
|
|||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('--linter', () => {
|
it('should generate the global eslint config', async () => {
|
||||||
describe('eslint', () => {
|
await lintInitGenerator(tree, {
|
||||||
it('should generate the global eslint config', async () => {
|
linter: Linter.EsLint,
|
||||||
await lintInitGenerator(tree, {
|
});
|
||||||
linter: Linter.EsLint,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
expect(tree.read('.eslintignore', 'utf-8')).toMatchInlineSnapshot(`
|
expect(tree.read('.eslintignore', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
"node_modules
|
"node_modules
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add the root eslint config to the lint targetDefaults for lint', async () => {
|
it('should add the root eslint config to the lint targetDefaults for lint', async () => {
|
||||||
await lintInitGenerator(tree, {
|
await lintInitGenerator(tree, {
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(readJson(tree, 'nx.json').targetDefaults.lint).toEqual({
|
expect(readJson(tree, 'nx.json').targetDefaults.lint).toEqual({
|
||||||
cache: true,
|
cache: true,
|
||||||
inputs: [
|
inputs: [
|
||||||
'default',
|
'default',
|
||||||
'{workspaceRoot}/.eslintrc.json',
|
'{workspaceRoot}/.eslintrc.json',
|
||||||
'{workspaceRoot}/.eslintignore',
|
'{workspaceRoot}/.eslintignore',
|
||||||
'{workspaceRoot}/eslint.config.js',
|
'{workspaceRoot}/eslint.config.js',
|
||||||
],
|
],
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not generate the global eslint config if it already exist', async () => {
|
|
||||||
tree.write('.eslintrc.js', '{}');
|
|
||||||
|
|
||||||
await lintInitGenerator(tree, {
|
|
||||||
linter: Linter.EsLint,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(tree.exists('.eslintrc.json')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not generate the global eslint config if it already exist', async () => {
|
||||||
|
tree.write('.eslintrc.js', '{}');
|
||||||
|
|
||||||
|
await lintInitGenerator(tree, {
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('.eslintrc.json')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup lint target defaults', async () => {
|
||||||
|
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
|
||||||
|
json.namedInputs ??= {};
|
||||||
|
json.namedInputs.production = ['default'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
|
||||||
|
await lintInitGenerator(tree, {});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults.lint
|
||||||
|
).toEqual({
|
||||||
|
cache: true,
|
||||||
|
inputs: [
|
||||||
|
'default',
|
||||||
|
'{workspaceRoot}/.eslintrc.json',
|
||||||
|
'{workspaceRoot}/.eslintignore',
|
||||||
|
'{workspaceRoot}/eslint.config.js',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup @nx/eslint/plugin', async () => {
|
||||||
|
process.env.NX_PCV3 = 'true';
|
||||||
|
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
|
||||||
|
json.namedInputs ??= {};
|
||||||
|
json.namedInputs.production = ['default'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
|
||||||
|
await lintInitGenerator(tree, {});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults.lint
|
||||||
|
).toEqual({
|
||||||
|
cache: true,
|
||||||
|
});
|
||||||
|
expect(readJson<NxJsonConfiguration>(tree, 'nx.json').plugins)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"targetName": "lint",
|
||||||
|
},
|
||||||
|
"plugin": "@nx/eslint/plugin",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
import { Linter } from '../utils/linter';
|
import { Linter } from '../utils/linter';
|
||||||
import { findEslintFile } from '../utils/eslint-file';
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
import { getGlobalEsLintConfiguration } from './global-eslint-config';
|
import { getGlobalEsLintConfiguration } from './global-eslint-config';
|
||||||
|
import { EslintPluginOptions } from '../../plugins/plugin';
|
||||||
|
|
||||||
export interface LinterInitOptions {
|
export interface LinterInitOptions {
|
||||||
linter?: Linter;
|
linter?: Linter;
|
||||||
@ -25,21 +26,25 @@ export interface LinterInitOptions {
|
|||||||
rootProject?: boolean;
|
rootProject?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTargetDefaults(tree: Tree) {
|
function updateProductionFileset(tree: Tree) {
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
|
|
||||||
const productionFileSet = nxJson.namedInputs?.production;
|
const productionFileSet = nxJson.namedInputs?.production;
|
||||||
if (productionFileSet) {
|
if (productionFileSet) {
|
||||||
// Remove .eslintrc.json
|
|
||||||
productionFileSet.push('!{projectRoot}/.eslintrc.json');
|
productionFileSet.push('!{projectRoot}/.eslintrc.json');
|
||||||
productionFileSet.push('!{projectRoot}/eslint.config.js');
|
productionFileSet.push('!{projectRoot}/eslint.config.js');
|
||||||
// Dedupe and set
|
// Dedupe and set
|
||||||
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
|
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
|
||||||
}
|
}
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTargetDefaults(tree: Tree) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
|
||||||
nxJson.targetDefaults ??= {};
|
nxJson.targetDefaults ??= {};
|
||||||
|
|
||||||
nxJson.targetDefaults.lint ??= {};
|
nxJson.targetDefaults.lint ??= {};
|
||||||
|
nxJson.targetDefaults.lint.cache ??= true;
|
||||||
nxJson.targetDefaults.lint.inputs ??= [
|
nxJson.targetDefaults.lint.inputs ??= [
|
||||||
'default',
|
'default',
|
||||||
`{workspaceRoot}/.eslintrc.json`,
|
`{workspaceRoot}/.eslintrc.json`,
|
||||||
@ -49,6 +54,42 @@ function addTargetDefaults(tree: Tree) {
|
|||||||
updateNxJson(tree, nxJson);
|
updateNxJson(tree, nxJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addPlugin(tree: Tree) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
|
||||||
|
for (const plugin of nxJson.plugins) {
|
||||||
|
if (
|
||||||
|
typeof plugin === 'string'
|
||||||
|
? plugin === '@nx/eslint/plugin'
|
||||||
|
: plugin.plugin === '@nx/eslint/plugin'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/eslint/plugin',
|
||||||
|
options: {
|
||||||
|
targetName: 'lint',
|
||||||
|
} as EslintPluginOptions,
|
||||||
|
});
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVSCodeExtensions(tree: Tree) {
|
||||||
|
if (tree.exists('.vscode/extensions.json')) {
|
||||||
|
updateJson(tree, '.vscode/extensions.json', (json) => {
|
||||||
|
json.recommendations ||= [];
|
||||||
|
const extension = 'dbaeumer.vscode-eslint';
|
||||||
|
if (!json.recommendations.includes(extension)) {
|
||||||
|
json.recommendations.push(extension);
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes ESLint configuration in a workspace and adds necessary dependencies.
|
* Initializes ESLint configuration in a workspace and adds necessary dependencies.
|
||||||
*/
|
*/
|
||||||
@ -67,19 +108,18 @@ function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
|
|||||||
getGlobalEsLintConfiguration(options.unitTestRunner, options.rootProject)
|
getGlobalEsLintConfiguration(options.unitTestRunner, options.rootProject)
|
||||||
);
|
);
|
||||||
tree.write('.eslintignore', 'node_modules\n');
|
tree.write('.eslintignore', 'node_modules\n');
|
||||||
addTargetDefaults(tree);
|
|
||||||
|
|
||||||
if (tree.exists('.vscode/extensions.json')) {
|
updateProductionFileset(tree);
|
||||||
updateJson(tree, '.vscode/extensions.json', (json) => {
|
|
||||||
json.recommendations ||= [];
|
const addPlugins = process.env.NX_PCV3 === 'true';
|
||||||
const extension = 'dbaeumer.vscode-eslint';
|
if (addPlugins) {
|
||||||
if (!json.recommendations.includes(extension)) {
|
addPlugin(tree);
|
||||||
json.recommendations.push(extension);
|
} else {
|
||||||
}
|
addTargetDefaults(tree);
|
||||||
return json;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateVSCodeExtensions(tree);
|
||||||
|
|
||||||
return !options.skipPackageJson
|
return !options.skipPackageJson
|
||||||
? addDependenciesToPackageJson(
|
? addDependenciesToPackageJson(
|
||||||
tree,
|
tree,
|
||||||
|
|||||||
@ -14,11 +14,7 @@ import {
|
|||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
|
|
||||||
import { Linter as LinterEnum } from '../utils/linter';
|
import { Linter as LinterEnum } from '../utils/linter';
|
||||||
import {
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
baseEsLintConfigFile,
|
|
||||||
baseEsLintFlatConfigFile,
|
|
||||||
findEslintFile,
|
|
||||||
} from '../utils/eslint-file';
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { lintInitGenerator } from '../init/init';
|
import { lintInitGenerator } from '../init/init';
|
||||||
import type { Linter } from 'eslint';
|
import type { Linter } from 'eslint';
|
||||||
@ -34,6 +30,10 @@ import {
|
|||||||
generateSpreadElement,
|
generateSpreadElement,
|
||||||
stringifyNodeList,
|
stringifyNodeList,
|
||||||
} from '../utils/flat-config/ast-utils';
|
} from '../utils/flat-config/ast-utils';
|
||||||
|
import {
|
||||||
|
baseEsLintConfigFile,
|
||||||
|
baseEsLintFlatConfigFile,
|
||||||
|
} from '../../utils/config-file';
|
||||||
|
|
||||||
interface LintProjectOptions {
|
interface LintProjectOptions {
|
||||||
project: string;
|
project: string;
|
||||||
@ -59,27 +59,46 @@ export async function lintProjectGenerator(
|
|||||||
});
|
});
|
||||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||||
|
|
||||||
projectConfig.targets['lint'] = {
|
|
||||||
executor: '@nx/eslint:lint',
|
|
||||||
outputs: ['{options.outputFile}'],
|
|
||||||
};
|
|
||||||
|
|
||||||
let lintFilePatterns = options.eslintFilePatterns;
|
let lintFilePatterns = options.eslintFilePatterns;
|
||||||
if (!lintFilePatterns && options.rootProject && projectConfig.root === '.') {
|
if (!lintFilePatterns && options.rootProject && projectConfig.root === '.') {
|
||||||
lintFilePatterns = ['./src'];
|
lintFilePatterns = ['./src'];
|
||||||
}
|
}
|
||||||
if (lintFilePatterns && lintFilePatterns.length) {
|
if (
|
||||||
if (
|
lintFilePatterns &&
|
||||||
isBuildableLibraryProject(projectConfig) &&
|
lintFilePatterns.length &&
|
||||||
!lintFilePatterns.includes('{projectRoot}')
|
!lintFilePatterns.includes('{projectRoot}') &&
|
||||||
) {
|
isBuildableLibraryProject(projectConfig)
|
||||||
lintFilePatterns.push(`{projectRoot}/package.json`);
|
) {
|
||||||
}
|
lintFilePatterns.push(`{projectRoot}/package.json`);
|
||||||
|
}
|
||||||
|
|
||||||
// only add lintFilePatterns if they are explicitly defined
|
const usePlugin = process.env.NX_PCV3 === 'true';
|
||||||
projectConfig.targets['lint'].options = {
|
if (usePlugin) {
|
||||||
lintFilePatterns,
|
if (
|
||||||
|
lintFilePatterns &&
|
||||||
|
lintFilePatterns.length &&
|
||||||
|
lintFilePatterns.some(
|
||||||
|
(p) => !['./src', '{projectRoot}', projectConfig.root].includes(p)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
projectConfig.targets['lint'] = {
|
||||||
|
command: `eslint ${lintFilePatterns
|
||||||
|
.join(' ')
|
||||||
|
.replace('{projectRoot}', projectConfig.root)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
projectConfig.targets['lint'] = {
|
||||||
|
executor: '@nx/eslint:lint',
|
||||||
|
outputs: ['{options.outputFile}'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (lintFilePatterns && lintFilePatterns.length) {
|
||||||
|
// only add lintFilePatterns if they are explicitly defined
|
||||||
|
projectConfig.targets['lint'].options = {
|
||||||
|
lintFilePatterns,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we are adding new project which is not the root project or
|
// we are adding new project which is not the root project or
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
baseEsLintConfigFile,
|
|
||||||
eslintConfigFileWhitelist,
|
|
||||||
findEslintFile,
|
findEslintFile,
|
||||||
lintConfigHasOverride,
|
lintConfigHasOverride,
|
||||||
} from './eslint-file';
|
} from './eslint-file';
|
||||||
|
|
||||||
import { Tree, readJson } from '@nx/devkit';
|
import { Tree, readJson } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import {
|
||||||
|
ESLINT_CONFIG_FILENAMES,
|
||||||
|
baseEsLintConfigFile,
|
||||||
|
} from '../../utils/config-file';
|
||||||
|
|
||||||
describe('@nx/eslint:lint-file', () => {
|
describe('@nx/eslint:lint-file', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
@ -21,7 +23,7 @@ describe('@nx/eslint:lint-file', () => {
|
|||||||
expect(findEslintFile(tree)).toBe(null);
|
expect(findEslintFile(tree)).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should return %p when calling findEslintFile',
|
'should return %p when calling findEslintFile',
|
||||||
(eslintFileName) => {
|
(eslintFileName) => {
|
||||||
tree.write(eslintFileName, '{}');
|
tree.write(eslintFileName, '{}');
|
||||||
@ -29,7 +31,7 @@ describe('@nx/eslint:lint-file', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should return base file instead %p when calling findEslintFile',
|
'should return base file instead %p when calling findEslintFile',
|
||||||
(eslintFileName) => {
|
(eslintFileName) => {
|
||||||
tree.write(baseEsLintConfigFile, '{}');
|
tree.write(baseEsLintConfigFile, '{}');
|
||||||
|
|||||||
@ -22,28 +22,24 @@ import {
|
|||||||
} from './flat-config/ast-utils';
|
} from './flat-config/ast-utils';
|
||||||
import ts = require('typescript');
|
import ts = require('typescript');
|
||||||
import { mapFilePath } from './flat-config/path-utils';
|
import { mapFilePath } from './flat-config/path-utils';
|
||||||
|
import {
|
||||||
|
baseEsLintConfigFile,
|
||||||
|
baseEsLintFlatConfigFile,
|
||||||
|
ESLINT_CONFIG_FILENAMES,
|
||||||
|
} from '../../utils/config-file';
|
||||||
|
|
||||||
export const eslintConfigFileWhitelist = [
|
export function findEslintFile(
|
||||||
'.eslintrc',
|
tree: Tree,
|
||||||
'.eslintrc.js',
|
projectRoot?: string
|
||||||
'.eslintrc.cjs',
|
): string | null {
|
||||||
'.eslintrc.yaml',
|
if (projectRoot === undefined && tree.exists(baseEsLintConfigFile)) {
|
||||||
'.eslintrc.yml',
|
|
||||||
'.eslintrc.json',
|
|
||||||
'eslint.config.js',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const baseEsLintConfigFile = '.eslintrc.base.json';
|
|
||||||
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
|
||||||
|
|
||||||
export function findEslintFile(tree: Tree, projectRoot = ''): string | null {
|
|
||||||
if (projectRoot === '' && tree.exists(baseEsLintConfigFile)) {
|
|
||||||
return baseEsLintConfigFile;
|
return baseEsLintConfigFile;
|
||||||
}
|
}
|
||||||
if (projectRoot === '' && tree.exists(baseEsLintFlatConfigFile)) {
|
if (projectRoot === undefined && tree.exists(baseEsLintFlatConfigFile)) {
|
||||||
return baseEsLintFlatConfigFile;
|
return baseEsLintFlatConfigFile;
|
||||||
}
|
}
|
||||||
for (const file of eslintConfigFileWhitelist) {
|
projectRoot ??= '';
|
||||||
|
for (const file of ESLINT_CONFIG_FILENAMES) {
|
||||||
if (tree.exists(joinPathFragments(projectRoot, file))) {
|
if (tree.exists(joinPathFragments(projectRoot, file))) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
@ -148,7 +144,7 @@ function offsetFilePath(
|
|||||||
tree: Tree
|
tree: Tree
|
||||||
): string {
|
): string {
|
||||||
if (
|
if (
|
||||||
eslintConfigFileWhitelist.some((eslintFile) =>
|
ESLINT_CONFIG_FILENAMES.some((eslintFile) =>
|
||||||
pathToFile.includes(eslintFile)
|
pathToFile.includes(eslintFile)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
updateNxJson,
|
updateNxJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import addEslintInputs from './add-eslint-inputs';
|
import addEslintInputs from './add-eslint-inputs';
|
||||||
import { eslintConfigFileWhitelist } from '../../generators/utils/eslint-file';
|
import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file';
|
||||||
|
|
||||||
describe('15.0.0 migration (add-eslint-inputs)', () => {
|
describe('15.0.0 migration (add-eslint-inputs)', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
@ -41,7 +41,7 @@ describe('15.0.0 migration (add-eslint-inputs)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should ignore %p for production',
|
'should ignore %p for production',
|
||||||
async (eslintConfigFilename) => {
|
async (eslintConfigFilename) => {
|
||||||
tree.write(eslintConfigFilename, '{}');
|
tree.write(eslintConfigFilename, '{}');
|
||||||
@ -57,7 +57,7 @@ describe('15.0.0 migration (add-eslint-inputs)', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should add %p to all lint targets',
|
'should add %p to all lint targets',
|
||||||
async (eslintConfigFilename) => {
|
async (eslintConfigFilename) => {
|
||||||
tree.write(eslintConfigFilename, '{}');
|
tree.write(eslintConfigFilename, '{}');
|
||||||
@ -95,7 +95,7 @@ describe('15.0.0 migration (add-eslint-inputs)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should not add `!{projectRoot}/%s` if `workspaceConfiguration.namedInputs` is undefined',
|
'should not add `!{projectRoot}/%s` if `workspaceConfiguration.namedInputs` is undefined',
|
||||||
async (eslintConfigFilename) => {
|
async (eslintConfigFilename) => {
|
||||||
tree.write(eslintConfigFilename, '{}');
|
tree.write(eslintConfigFilename, '{}');
|
||||||
@ -108,7 +108,7 @@ describe('15.0.0 migration (add-eslint-inputs)', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should not add `!{projectRoot}/%s` if `workspaceConfiguration.namedInputs.production` is undefined',
|
'should not add `!{projectRoot}/%s` if `workspaceConfiguration.namedInputs.production` is undefined',
|
||||||
async (eslintConfigFilename) => {
|
async (eslintConfigFilename) => {
|
||||||
updateNxJson(tree, {
|
updateNxJson(tree, {
|
||||||
@ -148,7 +148,7 @@ describe('15.0.0 migration (add-eslint-inputs)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(eslintConfigFileWhitelist)(
|
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||||
'should not override `targetDefaults.lint.inputs` with `%s` as there was a default target set in the workspace config',
|
'should not override `targetDefaults.lint.inputs` with `%s` as there was a default target set in the workspace config',
|
||||||
async (eslintConfigFilename) => {
|
async (eslintConfigFilename) => {
|
||||||
updateNxJson(tree, {
|
updateNxJson(tree, {
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import {
|
|||||||
Tree,
|
Tree,
|
||||||
updateNxJson,
|
updateNxJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { eslintConfigFileWhitelist } from '../../generators/utils/eslint-file';
|
|
||||||
import { getEslintTargets } from '../../generators/utils/eslint-targets';
|
import { getEslintTargets } from '../../generators/utils/eslint-targets';
|
||||||
|
import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file';
|
||||||
|
|
||||||
export default async function addEslintInputs(tree: Tree) {
|
export default async function addEslintInputs(tree: Tree) {
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
|
|
||||||
const globalEslintFile = eslintConfigFileWhitelist.find((file) =>
|
const globalEslintFile = ESLINT_CONFIG_FILENAMES.find((file) =>
|
||||||
tree.exists(file)
|
tree.exists(file)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import {
|
|||||||
Tree,
|
Tree,
|
||||||
updateNxJson,
|
updateNxJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { eslintConfigFileWhitelist } from '../../generators/utils/eslint-file';
|
|
||||||
import { getEslintTargets } from '../../generators/utils/eslint-targets';
|
import { getEslintTargets } from '../../generators/utils/eslint-targets';
|
||||||
|
import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file';
|
||||||
|
|
||||||
export default async function addEslintIgnore(tree: Tree) {
|
export default async function addEslintIgnore(tree: Tree) {
|
||||||
const nxJson = readJson(tree, 'nx.json');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
|
|
||||||
const globalEslintFile = eslintConfigFileWhitelist.find((file) =>
|
const globalEslintFile = ESLINT_CONFIG_FILENAMES.find((file) =>
|
||||||
tree.exists(file)
|
tree.exists(file)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
147
packages/eslint/src/plugins/plugin.spec.ts
Normal file
147
packages/eslint/src/plugins/plugin.spec.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { CreateNodesContext } from '@nx/devkit';
|
||||||
|
import { createNodes } from './plugin';
|
||||||
|
import { vol } from 'memfs';
|
||||||
|
|
||||||
|
jest.mock('fs', () => {
|
||||||
|
const memFs = require('memfs').fs;
|
||||||
|
return {
|
||||||
|
...memFs,
|
||||||
|
existsSync: (p) => (p.endsWith('.node') ? true : memFs.existsSync(p)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@nx/eslint/plugin', () => {
|
||||||
|
let createNodesFunction = createNodes[1];
|
||||||
|
let context: CreateNodesContext;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
context = {
|
||||||
|
nxJsonConfiguration: {
|
||||||
|
namedInputs: {
|
||||||
|
default: ['{projectRoot}/**/*'],
|
||||||
|
production: ['!{projectRoot}/**/*.spec.ts'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
workspaceRoot: '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vol.reset();
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create nodes with default configuration for nested project', () => {
|
||||||
|
const fileSys = {
|
||||||
|
'apps/my-app/.eslintrc.json': `{}`,
|
||||||
|
'apps/my-app/project.json': `{}`,
|
||||||
|
'.eslintrc.json': `{}`,
|
||||||
|
'package.json': `{}`,
|
||||||
|
};
|
||||||
|
vol.fromJSON(fileSys, '');
|
||||||
|
const nodes = createNodesFunction(
|
||||||
|
'apps/my-app/project.json',
|
||||||
|
{
|
||||||
|
targetName: 'lint',
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nodes).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"apps/my-app": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"{workspaceRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/apps/my-app/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "apps/my-app",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create nodes with default configuration for standalone project', () => {
|
||||||
|
const fileSys = {
|
||||||
|
'apps/my-app/eslint.config.js': `module.exports = []`,
|
||||||
|
'apps/my-app/project.json': `{}`,
|
||||||
|
'eslint.config.js': `module.exports = []`,
|
||||||
|
'src/index.ts': `console.log('hello world')`,
|
||||||
|
'package.json': `{}`,
|
||||||
|
};
|
||||||
|
vol.fromJSON(fileSys, '');
|
||||||
|
const nodes = createNodesFunction(
|
||||||
|
'package.json',
|
||||||
|
{
|
||||||
|
targetName: 'lint',
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nodes).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
".": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint ./src",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"{workspaceRoot}/eslint.config.js",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": ".",
|
||||||
|
"env": {
|
||||||
|
"ESLINT_USE_FLAT_CONFIG": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create nodes if no src folder for root', () => {
|
||||||
|
const fileSys = {
|
||||||
|
'apps/my-app/eslint.config.js': `module.exports = []`,
|
||||||
|
'apps/my-app/project.json': `{}`,
|
||||||
|
'eslint.config.js': `module.exports = []`,
|
||||||
|
'package.json': `{}`,
|
||||||
|
};
|
||||||
|
vol.fromJSON(fileSys, '');
|
||||||
|
const nodes = createNodesFunction(
|
||||||
|
'package.json',
|
||||||
|
{
|
||||||
|
targetName: 'lint',
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nodes).toMatchInlineSnapshot(`{}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
149
packages/eslint/src/plugins/plugin.ts
Normal file
149
packages/eslint/src/plugins/plugin.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import {
|
||||||
|
CreateNodes,
|
||||||
|
CreateNodesContext,
|
||||||
|
TargetConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
|
import { readdirSync } from 'fs';
|
||||||
|
import { combineGlobPatterns } from 'nx/src/utils/globs';
|
||||||
|
import {
|
||||||
|
ESLINT_CONFIG_FILENAMES,
|
||||||
|
findBaseEslintFile,
|
||||||
|
isFlatConfig,
|
||||||
|
} from '../utils/config-file';
|
||||||
|
|
||||||
|
export interface EslintPluginOptions {
|
||||||
|
targetName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNodes: CreateNodes<EslintPluginOptions> = [
|
||||||
|
combineGlobPatterns(['**/project.json', '**/package.json']),
|
||||||
|
(configFilePath, options, context) => {
|
||||||
|
const projectRoot = dirname(configFilePath);
|
||||||
|
|
||||||
|
options = normalizeOptions(options);
|
||||||
|
|
||||||
|
const eslintConfigs = getEslintConfigsForProject(
|
||||||
|
projectRoot,
|
||||||
|
context.workspaceRoot
|
||||||
|
);
|
||||||
|
if (!eslintConfigs.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
projects: {
|
||||||
|
[projectRoot]: {
|
||||||
|
targets: buildEslintTargets(
|
||||||
|
eslintConfigs,
|
||||||
|
projectRoot,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function getEslintConfigsForProject(
|
||||||
|
projectRoot: string,
|
||||||
|
workspaceRoot: string
|
||||||
|
): string[] {
|
||||||
|
const detectedConfigs = new Set<string>();
|
||||||
|
const baseConfig = findBaseEslintFile(workspaceRoot);
|
||||||
|
if (baseConfig) {
|
||||||
|
detectedConfigs.add(baseConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
let siblingFiles = readdirSync(join(workspaceRoot, projectRoot));
|
||||||
|
|
||||||
|
if (projectRoot === '.') {
|
||||||
|
// If there's no src folder, it's not a standalone project
|
||||||
|
if (!siblingFiles.includes('src')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// If it's standalone but doesn't have eslint config, it's not a lintable
|
||||||
|
const config = siblingFiles.find((f) =>
|
||||||
|
ESLINT_CONFIG_FILENAMES.includes(f)
|
||||||
|
);
|
||||||
|
if (!config) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
detectedConfigs.add(config);
|
||||||
|
return Array.from(detectedConfigs);
|
||||||
|
}
|
||||||
|
while (projectRoot !== '.') {
|
||||||
|
// if it has an eslint config it's lintable
|
||||||
|
const config = siblingFiles.find((f) =>
|
||||||
|
ESLINT_CONFIG_FILENAMES.includes(f)
|
||||||
|
);
|
||||||
|
if (config) {
|
||||||
|
detectedConfigs.add(`${projectRoot}/${config}`);
|
||||||
|
return Array.from(detectedConfigs);
|
||||||
|
}
|
||||||
|
projectRoot = dirname(projectRoot);
|
||||||
|
siblingFiles = readdirSync(join(workspaceRoot, projectRoot));
|
||||||
|
}
|
||||||
|
// check whether the root has an eslint config
|
||||||
|
const config = readdirSync(workspaceRoot).find((f) =>
|
||||||
|
ESLINT_CONFIG_FILENAMES.includes(f)
|
||||||
|
);
|
||||||
|
if (config) {
|
||||||
|
detectedConfigs.add(config);
|
||||||
|
return Array.from(detectedConfigs);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEslintTargets(
|
||||||
|
eslintConfigs: string[],
|
||||||
|
projectRoot: string,
|
||||||
|
options: EslintPluginOptions,
|
||||||
|
context: CreateNodesContext
|
||||||
|
) {
|
||||||
|
const targetDefaults = readTargetDefaultsForTarget(
|
||||||
|
options.targetName,
|
||||||
|
context.nxJsonConfiguration.targetDefaults,
|
||||||
|
'@nx/eslint:lint'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRootProject = projectRoot === '.';
|
||||||
|
|
||||||
|
const targets: Record<string, TargetConfiguration> = {};
|
||||||
|
|
||||||
|
const baseTargetConfig: TargetConfiguration = {
|
||||||
|
command: `eslint ${isRootProject ? './src' : '.'}`,
|
||||||
|
options: {
|
||||||
|
cwd: projectRoot,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (eslintConfigs.some((config) => isFlatConfig(config))) {
|
||||||
|
baseTargetConfig.options.env = {
|
||||||
|
ESLINT_USE_FLAT_CONFIG: 'true',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
targets[options.targetName] = {
|
||||||
|
...baseTargetConfig,
|
||||||
|
cache: targetDefaults?.cache ?? true,
|
||||||
|
inputs: targetDefaults?.inputs ?? [
|
||||||
|
'default',
|
||||||
|
...eslintConfigs.map((config) => `{workspaceRoot}/${config}`),
|
||||||
|
'{workspaceRoot}/tools/eslint-rules/**/*',
|
||||||
|
{ externalDependencies: ['eslint'] },
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
...baseTargetConfig.options,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOptions(options: EslintPluginOptions): EslintPluginOptions {
|
||||||
|
options ??= {};
|
||||||
|
options.targetName ??= 'lint';
|
||||||
|
return options;
|
||||||
|
}
|
||||||
35
packages/eslint/src/utils/config-file.ts
Normal file
35
packages/eslint/src/utils/config-file.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { joinPathFragments } from '@nx/devkit';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
export const ESLINT_CONFIG_FILENAMES = [
|
||||||
|
'.eslintrc',
|
||||||
|
'.eslintrc.js',
|
||||||
|
'.eslintrc.cjs',
|
||||||
|
'.eslintrc.yaml',
|
||||||
|
'.eslintrc.yml',
|
||||||
|
'.eslintrc.json',
|
||||||
|
'eslint.config.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const baseEsLintConfigFile = '.eslintrc.base.json';
|
||||||
|
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
||||||
|
|
||||||
|
export function findBaseEslintFile(workspaceRoot = ''): string | null {
|
||||||
|
if (existsSync(joinPathFragments(workspaceRoot, baseEsLintConfigFile))) {
|
||||||
|
return baseEsLintConfigFile;
|
||||||
|
}
|
||||||
|
if (existsSync(joinPathFragments(workspaceRoot, baseEsLintFlatConfigFile))) {
|
||||||
|
return baseEsLintFlatConfigFile;
|
||||||
|
}
|
||||||
|
for (const file of ESLINT_CONFIG_FILENAMES) {
|
||||||
|
if (existsSync(joinPathFragments(workspaceRoot, file))) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFlatConfig(configFilePath: string): boolean {
|
||||||
|
return configFilePath.endsWith('.config.js');
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user