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 () => {
|
// This is intentionally disabled, since we should always create a node for project that contains eslint config
|
||||||
createFiles({
|
// it('should not create nodes for nested projects without a root level eslint config when all files are ignored (.eslintignore)', async () => {
|
||||||
'apps/my-app/.eslintrc.json': `{}`,
|
// createFiles({
|
||||||
'apps/my-app/.eslintignore': `**/*`,
|
// 'apps/my-app/.eslintrc.json': `{}`,
|
||||||
'apps/my-app/project.json': `{}`,
|
// 'apps/my-app/.eslintignore': `**/*`,
|
||||||
'apps/my-app/index.ts': `console.log('hello world')`,
|
// 'apps/my-app/project.json': `{}`,
|
||||||
'libs/my-lib/.eslintrc.json': `{}`,
|
// 'apps/my-app/index.ts': `console.log('hello world')`,
|
||||||
'libs/my-lib/.eslintignore': `**/*`,
|
// 'libs/my-lib/.eslintrc.json': `{}`,
|
||||||
'libs/my-lib/project.json': `{}`,
|
// 'libs/my-lib/.eslintignore': `**/*`,
|
||||||
'libs/my-lib/index.ts': `console.log('hello world')`,
|
// 'libs/my-lib/project.json': `{}`,
|
||||||
});
|
// 'libs/my-lib/index.ts': `console.log('hello world')`,
|
||||||
expect(
|
// });
|
||||||
await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' })
|
// expect(
|
||||||
).toMatchInlineSnapshot(`
|
// await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' })
|
||||||
{
|
// ).toMatchInlineSnapshot(`
|
||||||
"projects": {},
|
// {
|
||||||
}
|
// "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 () => {
|
// This is intentionally disabled, since we should always create a node for project that contains eslint config
|
||||||
createFiles({
|
// it('should not create nodes for nested projects without a root level eslint config when all files are ignored (ignorePatterns in .eslintrc.json)', async () => {
|
||||||
'apps/my-app/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`,
|
// createFiles({
|
||||||
'apps/my-app/project.json': `{}`,
|
// 'apps/my-app/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`,
|
||||||
'apps/my-app/index.ts': `console.log('hello world')`,
|
// 'apps/my-app/project.json': `{}`,
|
||||||
'libs/my-lib/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`,
|
// 'apps/my-app/index.ts': `console.log('hello world')`,
|
||||||
'libs/my-lib/project.json': `{}`,
|
// 'libs/my-lib/.eslintrc.json': `{ "ignorePatterns": ["**/*"] }`,
|
||||||
'libs/my-lib/index.ts': `console.log('hello world')`,
|
// 'libs/my-lib/project.json': `{}`,
|
||||||
});
|
// 'libs/my-lib/index.ts': `console.log('hello world')`,
|
||||||
expect(
|
// });
|
||||||
await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' })
|
// expect(
|
||||||
).toMatchInlineSnapshot(`
|
// await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' })
|
||||||
{
|
// ).toMatchInlineSnapshot(`
|
||||||
"projects": {},
|
// {
|
||||||
}
|
// "projects": {},
|
||||||
`);
|
// }
|
||||||
});
|
// `);
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('root eslint config and nested eslint configs', () => {
|
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 () => {
|
it('should handle multiple levels of nesting and ignored files correctly', async () => {
|
||||||
createFiles({
|
createFiles({
|
||||||
'.eslintrc.json': '{ "root": true, "ignorePatterns": ["**/*"] }',
|
'.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/project.json': '{}',
|
||||||
'apps/myapp/index.ts': 'console.log("hello world")',
|
'apps/myapp/index.ts': 'console.log("hello world")',
|
||||||
'apps/myapp/nested/mylib/.eslintrc.json': JSON.stringify({
|
'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 { combineGlobPatterns } from 'nx/src/utils/globs';
|
||||||
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
|
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
|
||||||
import { gte } from 'semver';
|
import { gte } from 'semver';
|
||||||
|
import type { ESLint as ESLintType } from 'eslint';
|
||||||
import {
|
import {
|
||||||
baseEsLintConfigFile,
|
baseEsLintConfigFile,
|
||||||
BASE_ESLINT_CONFIG_FILENAMES,
|
BASE_ESLINT_CONFIG_FILENAMES,
|
||||||
@ -186,6 +187,7 @@ const internalCreateNodes = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const internalCreateNodesV2 = async (
|
const internalCreateNodesV2 = async (
|
||||||
|
ESLint: typeof ESLintType,
|
||||||
configFilePath: string,
|
configFilePath: string,
|
||||||
options: EslintPluginOptions,
|
options: EslintPluginOptions,
|
||||||
context: CreateNodesContextV2,
|
context: CreateNodesContextV2,
|
||||||
@ -195,10 +197,6 @@ const internalCreateNodesV2 = async (
|
|||||||
hashByRoot: Map<string, string>
|
hashByRoot: Map<string, string>
|
||||||
): Promise<CreateNodesResult> => {
|
): Promise<CreateNodesResult> => {
|
||||||
const configDir = dirname(configFilePath);
|
const configDir = dirname(configFilePath);
|
||||||
|
|
||||||
const ESLint = await resolveESLintClass({
|
|
||||||
useFlatConfigOverrideVal: isFlatConfig(configFilePath),
|
|
||||||
});
|
|
||||||
const eslintVersion = ESLint.version;
|
const eslintVersion = ESLint.version;
|
||||||
|
|
||||||
const projects: CreateNodesResult['projects'] = {};
|
const projects: CreateNodesResult['projects'] = {};
|
||||||
@ -212,16 +210,22 @@ const internalCreateNodesV2 = async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasNonIgnoredLintableFiles = false;
|
||||||
|
if (configDir !== projectRoot || projectRoot === '.') {
|
||||||
const eslint = new ESLint({
|
const eslint = new ESLint({
|
||||||
cwd: join(context.workspaceRoot, projectRoot),
|
cwd: join(context.workspaceRoot, projectRoot),
|
||||||
});
|
});
|
||||||
let hasNonIgnoredLintableFiles = false;
|
|
||||||
for (const file of lintableFilesPerProjectRoot.get(projectRoot) ?? []) {
|
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;
|
hasNonIgnoredLintableFiles = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
hasNonIgnoredLintableFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasNonIgnoredLintableFiles) {
|
if (!hasNonIgnoredLintableFiles) {
|
||||||
// No lintable files in the project, store in the cache and skip further processing
|
// 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]])
|
projectRoots.map((r, i) => [r, hashes[i]])
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
if (eslintConfigFiles.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const ESLint = await resolveESLintClass({
|
||||||
|
useFlatConfigOverrideVal: isFlatConfig(eslintConfigFiles[0]),
|
||||||
|
});
|
||||||
return await createNodesFromFiles(
|
return await createNodesFromFiles(
|
||||||
(configFile, options, context) =>
|
(configFile, options, context) =>
|
||||||
internalCreateNodesV2(
|
internalCreateNodesV2(
|
||||||
|
ESLint,
|
||||||
configFile,
|
configFile,
|
||||||
options,
|
options,
|
||||||
context,
|
context,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user