fix(linter): speed up inferred plugin node processing (#31281)
This PR improves the **createNodes** function of eslint's inferred plugin by making two pragmatic choices: - reusing the ESLint between config file's runs instead of recreating the new one every time - skipping ignored files checks for projects that already have eslint config file ## Results of benchmarks on customer's repo: ### Without ESLint plugin - create-project-graph-async - avg. 11739.1326225 -> 11 seconds ### With current ESLint plugin - create-project-graph-async - avg. 98005.0965135 -> 98 seconds ### With modified ESLint plugin - create-project-graph-async - avg. 13225.073817 -> 13 seconds - (@nx/eslint/plugin:createNodes - 2206.96497, 16.69%) ## 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 #
This commit is contained in:
parent
3a33d5f54f
commit
d78782da49
@ -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({
|
||||
|
||||
@ -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<string, string>
|
||||
): Promise<CreateNodesResult> => {
|
||||
const configDir = dirname(configFilePath);
|
||||
|
||||
const ESLint = await resolveESLintClass({
|
||||
useFlatConfigOverrideVal: isFlatConfig(configFilePath),
|
||||
});
|
||||
const eslintVersion = ESLint.version;
|
||||
|
||||
const projects: CreateNodesResult['projects'] = {};
|
||||
@ -212,16 +210,22 @@ const internalCreateNodesV2 = async (
|
||||
return;
|
||||
}
|
||||
|
||||
let hasNonIgnoredLintableFiles = false;
|
||||
if (configDir !== projectRoot || projectRoot === '.') {
|
||||
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)))) {
|
||||
if (
|
||||
!(await eslint.isPathIgnored(join(context.workspaceRoot, file)))
|
||||
) {
|
||||
hasNonIgnoredLintableFiles = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasNonIgnoredLintableFiles = true;
|
||||
}
|
||||
|
||||
if (!hasNonIgnoredLintableFiles) {
|
||||
// No lintable files in the project, store in the cache and skip further processing
|
||||
@ -286,9 +290,16 @@ export const createNodesV2: CreateNodesV2<EslintPluginOptions> = [
|
||||
projectRoots.map((r, i) => [r, hashes[i]])
|
||||
);
|
||||
try {
|
||||
if (eslintConfigFiles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const ESLint = await resolveESLintClass({
|
||||
useFlatConfigOverrideVal: isFlatConfig(eslintConfigFiles[0]),
|
||||
});
|
||||
return await createNodesFromFiles(
|
||||
(configFile, options, context) =>
|
||||
internalCreateNodesV2(
|
||||
ESLint,
|
||||
configFile,
|
||||
options,
|
||||
context,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user