From 1a2d406f5082f6a05f5c5c7c3ce1d1254b7bc684 Mon Sep 17 00:00:00 2001 From: Miroslav Jonas Date: Tue, 20 May 2025 12:38:27 +0200 Subject: [PATCH 1/3] fix(linter): speed up inferred plugin node processing --- packages/eslint/src/plugins/plugin.ts | 30 +++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/eslint/src/plugins/plugin.ts b/packages/eslint/src/plugins/plugin.ts index 913df91680efbe..6c81251ec390f5 100644 --- a/packages/eslint/src/plugins/plugin.ts +++ b/packages/eslint/src/plugins/plugin.ts @@ -22,6 +22,7 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { combineGlobPatterns } from 'nx/src/utils/globs'; import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context'; import { gte } from 'semver'; +import type { ESLint as ESLintType } from 'eslint'; import { baseEsLintConfigFile, BASE_ESLINT_CONFIG_FILENAMES, @@ -186,6 +187,7 @@ const internalCreateNodes = async ( }; const internalCreateNodesV2 = async ( + ESLint: typeof ESLintType, configFilePath: string, options: EslintPluginOptions, context: CreateNodesContextV2, @@ -195,10 +197,6 @@ const internalCreateNodesV2 = async ( hashByRoot: Map ): Promise => { const configDir = dirname(configFilePath); - - const ESLint = await resolveESLintClass({ - useFlatConfigOverrideVal: isFlatConfig(configFilePath), - }); const eslintVersion = ESLint.version; const projects: CreateNodesResult['projects'] = {}; @@ -212,15 +210,21 @@ const internalCreateNodesV2 = async ( return; } - const eslint = new ESLint({ - cwd: join(context.workspaceRoot, projectRoot), - }); let hasNonIgnoredLintableFiles = false; - for (const file of lintableFilesPerProjectRoot.get(projectRoot) ?? []) { - if (!(await eslint.isPathIgnored(join(context.workspaceRoot, file)))) { - hasNonIgnoredLintableFiles = true; - break; + if (configDir !== projectRoot) { + const eslint = new ESLint({ + cwd: join(context.workspaceRoot, projectRoot), + }); + for (const file of lintableFilesPerProjectRoot.get(projectRoot) ?? []) { + if ( + !(await eslint.isPathIgnored(join(context.workspaceRoot, file))) + ) { + hasNonIgnoredLintableFiles = true; + break; + } } + } else { + hasNonIgnoredLintableFiles = true; } if (!hasNonIgnoredLintableFiles) { @@ -286,9 +290,13 @@ export const createNodesV2: CreateNodesV2 = [ projectRoots.map((r, i) => [r, hashes[i]]) ); try { + const ESLint = await resolveESLintClass({ + useFlatConfigOverrideVal: isFlatConfig(configFiles[0]), + }); return await createNodesFromFiles( (configFile, options, context) => internalCreateNodesV2( + ESLint, configFile, options, context, From 4d55ff041ba614fbe0ed345d9ebbe511237beec6 Mon Sep 17 00:00:00 2001 From: Miroslav Jonas Date: Wed, 21 May 2025 14:21:43 +0200 Subject: [PATCH 2/3] fix(linter): fix broken test --- packages/eslint/src/plugins/plugin.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/eslint/src/plugins/plugin.ts b/packages/eslint/src/plugins/plugin.ts index 6c81251ec390f5..094e0d683a6c96 100644 --- a/packages/eslint/src/plugins/plugin.ts +++ b/packages/eslint/src/plugins/plugin.ts @@ -290,8 +290,11 @@ export const createNodesV2: CreateNodesV2 = [ projectRoots.map((r, i) => [r, hashes[i]]) ); try { + if (eslintConfigFiles.length === 0) { + return []; + } const ESLint = await resolveESLintClass({ - useFlatConfigOverrideVal: isFlatConfig(configFiles[0]), + useFlatConfigOverrideVal: isFlatConfig(eslintConfigFiles[0]), }); return await createNodesFromFiles( (configFile, options, context) => From 0a0a7c58cd155989047c33046a80692d85b1e2ea Mon Sep 17 00:00:00 2001 From: Miroslav Jonas Date: Wed, 21 May 2025 17:12:45 +0200 Subject: [PATCH 3/3] fix(linter): fix tests --- packages/eslint/src/plugins/plugin.spec.ts | 75 +++++++++++----------- packages/eslint/src/plugins/plugin.ts | 2 +- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/eslint/src/plugins/plugin.spec.ts b/packages/eslint/src/plugins/plugin.spec.ts index 7470d906a2983b..346370ee13ca8b 100644 --- a/packages/eslint/src/plugins/plugin.spec.ts +++ b/packages/eslint/src/plugins/plugin.spec.ts @@ -518,43 +518,45 @@ describe('@nx/eslint/plugin', () => { `); }); - it('should not create nodes for nested projects without a root level eslint config when all files are ignored (.eslintignore)', async () => { - createFiles({ - 'apps/my-app/.eslintrc.json': `{}`, - 'apps/my-app/.eslintignore': `**/*`, - 'apps/my-app/project.json': `{}`, - 'apps/my-app/index.ts': `console.log('hello world')`, - 'libs/my-lib/.eslintrc.json': `{}`, - 'libs/my-lib/.eslintignore': `**/*`, - 'libs/my-lib/project.json': `{}`, - 'libs/my-lib/index.ts': `console.log('hello world')`, - }); - expect( - await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' }) - ).toMatchInlineSnapshot(` - { - "projects": {}, - } - `); - }); + // This is intentionally disabled, since we should always create a node for project that contains eslint config + // it('should not create nodes for nested projects without a root level eslint config when all files are ignored (.eslintignore)', async () => { + // createFiles({ + // 'apps/my-app/.eslintrc.json': `{}`, + // 'apps/my-app/.eslintignore': `**/*`, + // 'apps/my-app/project.json': `{}`, + // 'apps/my-app/index.ts': `console.log('hello world')`, + // 'libs/my-lib/.eslintrc.json': `{}`, + // 'libs/my-lib/.eslintignore': `**/*`, + // 'libs/my-lib/project.json': `{}`, + // 'libs/my-lib/index.ts': `console.log('hello world')`, + // }); + // expect( + // await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' }) + // ).toMatchInlineSnapshot(` + // { + // "projects": {}, + // } + // `); + // }); - it('should not create nodes for nested projects without a root level eslint config when all files are ignored (ignorePatterns in .eslintrc.json)', async () => { - createFiles({ - 'apps/my-app/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`, - 'apps/my-app/project.json': `{}`, - 'apps/my-app/index.ts': `console.log('hello world')`, - 'libs/my-lib/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`, - 'libs/my-lib/project.json': `{}`, - 'libs/my-lib/index.ts': `console.log('hello world')`, - }); - expect( - await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' }) - ).toMatchInlineSnapshot(` - { - "projects": {}, - } - `); - }); + // This is intentionally disabled, since we should always create a node for project that contains eslint config + // it('should not create nodes for nested projects without a root level eslint config when all files are ignored (ignorePatterns in .eslintrc.json)', async () => { + // createFiles({ + // 'apps/my-app/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`, + // 'apps/my-app/project.json': `{}`, + // 'apps/my-app/index.ts': `console.log('hello world')`, + // 'libs/my-lib/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`, + // 'libs/my-lib/project.json': `{}`, + // 'libs/my-lib/index.ts': `console.log('hello world')`, + // }); + // expect( + // await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' }) + // ).toMatchInlineSnapshot(` + // { + // "projects": {}, + // } + // `); + // }); }); describe('root eslint config and nested eslint configs', () => { @@ -721,7 +723,6 @@ describe('@nx/eslint/plugin', () => { it('should handle multiple levels of nesting and ignored files correctly', async () => { createFiles({ '.eslintrc.json': '{ "root": true, "ignorePatterns": ["**/*"] }', - 'apps/myapp/.eslintrc.json': '{ "extends": "../../.eslintrc.json" }', // no lintable files, don't create task 'apps/myapp/project.json': '{}', 'apps/myapp/index.ts': 'console.log("hello world")', 'apps/myapp/nested/mylib/.eslintrc.json': JSON.stringify({ diff --git a/packages/eslint/src/plugins/plugin.ts b/packages/eslint/src/plugins/plugin.ts index 094e0d683a6c96..dd4a56e666443a 100644 --- a/packages/eslint/src/plugins/plugin.ts +++ b/packages/eslint/src/plugins/plugin.ts @@ -211,7 +211,7 @@ const internalCreateNodesV2 = async ( } let hasNonIgnoredLintableFiles = false; - if (configDir !== projectRoot) { + if (configDir !== projectRoot || projectRoot === '.') { const eslint = new ESLint({ cwd: join(context.workspaceRoot, projectRoot), });