Skip to content

Commit cef8e7c

Browse files
committed
feat(plugin-eslint): nx helpers generate array of lint targets
1 parent c9345a5 commit cef8e7c

File tree

5 files changed

+108
-154
lines changed

5 files changed

+108
-154
lines changed

packages/plugin-eslint/src/lib/nx.integration.test.ts

+53-77
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { dirname, join } from 'node:path';
22
import { fileURLToPath } from 'node:url';
33
import { setWorkspaceRoot, workspaceRoot } from 'nx/src/utils/workspace-root';
44
import type { MockInstance } from 'vitest';
5-
import { ESLintPluginConfig } from './config';
5+
import { type ESLintTarget } from './config';
66
import { eslintConfigFromNxProject, eslintConfigFromNxProjects } from './nx';
77

88
describe('Nx helpers', () => {
@@ -33,63 +33,53 @@ describe('Nx helpers', () => {
3333

3434
describe('create config from all Nx projects', () => {
3535
it('should include eslintrc and patterns of each project', async () => {
36-
await expect(eslintConfigFromNxProjects()).resolves.toEqual({
37-
eslintrc: {
38-
root: true,
39-
overrides: [
40-
{
41-
files: ['packages/cli/**/*.ts', 'packages/cli/package.json'],
42-
extends: './packages/cli/.eslintrc.json',
43-
},
44-
{
45-
files: ['packages/core/**/*.ts', 'packages/core/package.json'],
46-
extends: './packages/core/.eslintrc.json',
47-
},
48-
{
49-
files: [
50-
'packages/nx-plugin/**/*.ts',
51-
'packages/nx-plugin/package.json',
52-
'packages/nx-plugin/generators.json',
53-
],
54-
extends: './packages/nx-plugin/.eslintrc.json',
55-
},
56-
{
57-
files: ['packages/utils/**/*.ts', 'packages/utils/package.json'],
58-
extends: './packages/utils/.eslintrc.json',
59-
},
36+
await expect(eslintConfigFromNxProjects()).resolves.toEqual([
37+
{
38+
eslintrc: './packages/cli/.eslintrc.json',
39+
patterns: [
40+
'packages/cli/**/*.ts',
41+
'packages/cli/package.json',
42+
'packages/cli/src/*.spec.ts',
43+
'packages/cli/src/*.cy.ts',
44+
'packages/cli/src/*.stories.ts',
45+
'packages/cli/src/.storybook/main.ts',
6046
],
6147
},
62-
patterns: [
63-
'packages/cli/**/*.ts',
64-
'packages/cli/package.json',
65-
'packages/cli/src/*.spec.ts',
66-
'packages/cli/src/*.cy.ts',
67-
'packages/cli/src/*.stories.ts',
68-
'packages/cli/src/.storybook/main.ts',
69-
70-
'packages/core/**/*.ts',
71-
'packages/core/package.json',
72-
'packages/core/src/*.spec.ts',
73-
'packages/core/src/*.cy.ts',
74-
'packages/core/src/*.stories.ts',
75-
'packages/core/src/.storybook/main.ts',
76-
77-
'packages/nx-plugin/**/*.ts',
78-
'packages/nx-plugin/package.json',
79-
'packages/nx-plugin/generators.json',
80-
'packages/nx-plugin/src/*.spec.ts',
81-
'packages/nx-plugin/src/*.cy.ts',
82-
'packages/nx-plugin/src/*.stories.ts',
83-
'packages/nx-plugin/src/.storybook/main.ts',
84-
85-
'packages/utils/**/*.ts',
86-
'packages/utils/package.json',
87-
'packages/utils/src/*.spec.ts',
88-
'packages/utils/src/*.cy.ts',
89-
'packages/utils/src/*.stories.ts',
90-
'packages/utils/src/.storybook/main.ts',
91-
],
92-
} satisfies ESLintPluginConfig);
48+
{
49+
eslintrc: './packages/core/.eslintrc.json',
50+
patterns: [
51+
'packages/core/**/*.ts',
52+
'packages/core/package.json',
53+
'packages/core/src/*.spec.ts',
54+
'packages/core/src/*.cy.ts',
55+
'packages/core/src/*.stories.ts',
56+
'packages/core/src/.storybook/main.ts',
57+
],
58+
},
59+
{
60+
eslintrc: './packages/nx-plugin/.eslintrc.json',
61+
patterns: [
62+
'packages/nx-plugin/**/*.ts',
63+
'packages/nx-plugin/package.json',
64+
'packages/nx-plugin/generators.json',
65+
'packages/nx-plugin/src/*.spec.ts',
66+
'packages/nx-plugin/src/*.cy.ts',
67+
'packages/nx-plugin/src/*.stories.ts',
68+
'packages/nx-plugin/src/.storybook/main.ts',
69+
],
70+
},
71+
{
72+
eslintrc: './packages/utils/.eslintrc.json',
73+
patterns: [
74+
'packages/utils/**/*.ts',
75+
'packages/utils/package.json',
76+
'packages/utils/src/*.spec.ts',
77+
'packages/utils/src/*.cy.ts',
78+
'packages/utils/src/*.stories.ts',
79+
'packages/utils/src/.storybook/main.ts',
80+
],
81+
},
82+
] satisfies ESLintTarget[]);
9383
});
9484
});
9585

@@ -119,28 +109,14 @@ describe('Nx helpers', () => {
119109
])(
120110
'project %j - expected configurations for projects %j',
121111
async (project, expectedProjects) => {
122-
const otherProjects = ALL_PROJECTS.filter(
123-
p => !expectedProjects.includes(p),
124-
);
125-
126-
const config = await eslintConfigFromNxProject(project);
112+
const targets = await eslintConfigFromNxProject(project);
127113

128-
expect(config.eslintrc).toEqual({
129-
root: true,
130-
overrides: expectedProjects.map(p => ({
131-
files: expect.arrayContaining([`packages/${p}/**/*.ts`]),
132-
extends: `./packages/${p}/.eslintrc.json`,
133-
})),
134-
});
135-
136-
expect(config.patterns).toEqual(
137-
expect.arrayContaining(
138-
expectedProjects.map(p => `packages/${p}/**/*.ts`),
139-
),
140-
);
141-
expect(config.patterns).toEqual(
142-
expect.not.arrayContaining(
143-
otherProjects.map(p => `packages/${p}/**/*.ts`),
114+
expect(targets).toEqual(
115+
expectedProjects.map(
116+
(p): ESLintTarget => ({
117+
eslintrc: `./packages/${p}/.eslintrc.json`,
118+
patterns: expect.arrayContaining([`packages/${p}/**/*.ts`]),
119+
}),
144120
),
145121
);
146122
},

packages/plugin-eslint/src/lib/nx/find-all-projects.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ESLintPluginConfig } from '../config';
1+
import type { ESLintTarget } from '../config';
22
import { nxProjectsToConfig } from './projects-to-config';
33

44
/**
@@ -22,7 +22,7 @@ import { nxProjectsToConfig } from './projects-to-config';
2222
*
2323
* @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin}
2424
*/
25-
export async function eslintConfigFromNxProjects(): Promise<ESLintPluginConfig> {
25+
export async function eslintConfigFromNxProjects(): Promise<ESLintTarget[]> {
2626
const { createProjectGraphAsync } = await import('@nx/devkit');
2727
const projectGraph = await createProjectGraphAsync({ exitOnError: false });
2828
return nxProjectsToConfig(projectGraph);

packages/plugin-eslint/src/lib/nx/find-project-with-deps.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ESLintPluginConfig } from '../config';
1+
import type { ESLintTarget } from '../config';
22
import { nxProjectsToConfig } from './projects-to-config';
33
import { findAllDependencies } from './traverse-graph';
44

@@ -28,7 +28,7 @@ import { findAllDependencies } from './traverse-graph';
2828
*/
2929
export async function eslintConfigFromNxProject(
3030
projectName: string,
31-
): Promise<ESLintPluginConfig> {
31+
): Promise<ESLintTarget[]> {
3232
const { createProjectGraphAsync } = await import('@nx/devkit');
3333
const projectGraph = await createProjectGraphAsync({ exitOnError: false });
3434

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ProjectConfiguration, ProjectGraph } from '@nx/devkit';
2-
import type { ESLint } from 'eslint';
3-
import type { ESLintPluginConfig } from '../config';
2+
import type { ESLintTarget } from '../config';
43
import {
54
findCodePushupEslintrc,
65
getEslintConfig,
@@ -10,7 +9,7 @@ import {
109
export async function nxProjectsToConfig(
1110
projectGraph: ProjectGraph,
1211
predicate: (project: ProjectConfiguration) => boolean = () => true,
13-
): Promise<ESLintPluginConfig> {
12+
): Promise<ESLintTarget[]> {
1413
// find Nx projects with lint target
1514
const { readProjectsConfigurationFromProjectGraph } = await import(
1615
'@nx/devkit'
@@ -22,32 +21,22 @@ export async function nxProjectsToConfig(
2221
.filter(predicate) // apply predicate
2322
.sort((a, b) => a.root.localeCompare(b.root));
2423

25-
// create single ESLint config with project-specific overrides
26-
const eslintConfig: ESLint.ConfigData = {
27-
root: true,
28-
overrides: await Promise.all(
29-
projects.map(async project => ({
30-
files: getLintFilePatterns(project),
31-
extends:
24+
return Promise.all(
25+
projects.map(
26+
async (project): Promise<ESLintTarget> => ({
27+
eslintrc:
3228
(await findCodePushupEslintrc(project)) ?? getEslintConfig(project),
33-
})),
29+
patterns: [
30+
...getLintFilePatterns(project),
31+
// HACK: ESLint.calculateConfigForFile won't find rules included only for subsets of *.ts when globs used
32+
// so we explicitly provide additional patterns used by @code-pushup/eslint-config to ensure those rules are included
33+
// this workaround won't be necessary once flat configs are stable (much easier to find all rules)
34+
`${project.sourceRoot}/*.spec.ts`, // jest/* and vitest/* rules
35+
`${project.sourceRoot}/*.cy.ts`, // cypress/* rules
36+
`${project.sourceRoot}/*.stories.ts`, // storybook/* rules
37+
`${project.sourceRoot}/.storybook/main.ts`, // storybook/no-uninstalled-addons rule
38+
],
39+
}),
3440
),
35-
};
36-
37-
// include patterns from each project
38-
const patterns = projects.flatMap(project => [
39-
...getLintFilePatterns(project),
40-
// HACK: ESLint.calculateConfigForFile won't find rules included only for subsets of *.ts when globs used
41-
// so we explicitly provide additional patterns used by @code-pushup/eslint-config to ensure those rules are included
42-
// this workaround won't be necessary once flat configs are stable (much easier to find all rules)
43-
`${project.sourceRoot}/*.spec.ts`, // jest/* and vitest/* rules
44-
`${project.sourceRoot}/*.cy.ts`, // cypress/* rules
45-
`${project.sourceRoot}/*.stories.ts`, // storybook/* rules
46-
`${project.sourceRoot}/.storybook/main.ts`, // storybook/no-uninstalled-addons rule
47-
]);
48-
49-
return {
50-
eslintrc: eslintConfig,
51-
patterns,
52-
};
41+
);
5342
}

packages/plugin-eslint/src/lib/nx/projects-to-config.unit.test.ts

+33-44
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import type {
33
ProjectGraphDependency,
44
ProjectGraphProjectNode,
55
} from '@nx/devkit';
6-
import type { ESLint } from 'eslint';
76
import { vol } from 'memfs';
87
import type { MockInstance } from 'vitest';
98
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
10-
import type { ESLintPluginConfig } from '../config';
9+
import type { ESLintPluginConfig, ESLintTarget } from '../config';
1110
import { nxProjectsToConfig } from './projects-to-config';
1211

1312
describe('nxProjectsToConfig', () => {
@@ -28,6 +27,7 @@ describe('nxProjectsToConfig', () => {
2827
},
2928
},
3029
},
30+
sourceRoot: `${node.data.root}/src`,
3131
...node.data,
3232
},
3333
},
@@ -65,20 +65,19 @@ describe('nxProjectsToConfig', () => {
6565
]);
6666

6767
const config = await nxProjectsToConfig(projectGraph);
68-
const { overrides } = config.eslintrc as ESLint.ConfigData;
6968

70-
expect(overrides).toEqual([
69+
expect(config).toEqual<ESLintPluginConfig>([
7170
{
72-
files: ['apps/client/**/*.ts'],
73-
extends: './apps/client/.eslintrc.json',
71+
eslintrc: './apps/client/.eslintrc.json',
72+
patterns: expect.arrayContaining(['apps/client/**/*.ts']),
7473
},
7574
{
76-
files: ['apps/server/**/*.ts'],
77-
extends: './apps/server/.eslintrc.json',
75+
eslintrc: './apps/server/.eslintrc.json',
76+
patterns: expect.arrayContaining(['apps/server/**/*.ts']),
7877
},
7978
{
80-
files: ['libs/models/**/*.ts'],
81-
extends: './libs/models/.eslintrc.json',
79+
eslintrc: './libs/models/.eslintrc.json',
80+
patterns: expect.arrayContaining(['libs/models/**/*.ts']),
8281
},
8382
]);
8483
});
@@ -107,11 +106,10 @@ describe('nxProjectsToConfig', () => {
107106
project => project.projectType === 'library',
108107
);
109108

110-
const { overrides } = config.eslintrc as ESLint.ConfigData;
111-
expect(overrides).toEqual([
109+
expect(config).toEqual<ESLintPluginConfig>([
112110
{
113-
files: ['libs/models/**/*.ts'],
114-
extends: './libs/models/.eslintrc.json',
111+
eslintrc: './libs/models/.eslintrc.json',
112+
patterns: expect.arrayContaining(['libs/models/**/*.ts']),
115113
},
116114
]);
117115
});
@@ -150,15 +148,14 @@ describe('nxProjectsToConfig', () => {
150148

151149
const config = await nxProjectsToConfig(projectGraph);
152150

153-
const { overrides } = config.eslintrc as ESLint.ConfigData;
154-
expect(overrides).toEqual([
151+
expect(config).toEqual<ESLintPluginConfig>([
155152
{
156-
files: ['apps/client/**/*.ts'],
157-
extends: './apps/client/.eslintrc.json',
153+
eslintrc: './apps/client/.eslintrc.json',
154+
patterns: expect.arrayContaining(['apps/client/**/*.ts']),
158155
},
159156
{
160-
files: ['apps/server/**/*.ts'],
161-
extends: './apps/server/.eslintrc.json',
157+
eslintrc: './apps/server/.eslintrc.json',
158+
patterns: expect.arrayContaining(['apps/server/**/*.ts']),
162159
},
163160
]);
164161
});
@@ -167,7 +164,7 @@ describe('nxProjectsToConfig', () => {
167164
vol.fromJSON(
168165
{
169166
'apps/client/code-pushup.eslintrc.json':
170-
'{ "extends": "@code-pushup" }',
167+
'{ "eslintrc": "@code-pushup" }',
171168
},
172169
MEMFS_VOLUME,
173170
);
@@ -177,10 +174,9 @@ describe('nxProjectsToConfig', () => {
177174

178175
const config = await nxProjectsToConfig(projectGraph);
179176

180-
const { overrides } = config.eslintrc as ESLint.ConfigData;
181-
expect(overrides).toEqual([
182-
expect.objectContaining({
183-
extends: './apps/client/code-pushup.eslintrc.json',
177+
expect(config).toEqual([
178+
expect.objectContaining<Partial<ESLintTarget>>({
179+
eslintrc: './apps/client/code-pushup.eslintrc.json',
184180
}),
185181
]);
186182
});
@@ -220,25 +216,18 @@ describe('nxProjectsToConfig', () => {
220216
},
221217
]);
222218

223-
await expect(nxProjectsToConfig(projectGraph)).resolves.toEqual({
224-
eslintrc: {
225-
root: true,
226-
overrides: [
227-
{
228-
files: ['apps/client/**/*.ts', 'apps/client/**/*.html'],
229-
extends: './apps/client/.eslintrc.json',
230-
},
231-
{
232-
files: ['apps/server/**/*.ts'],
233-
extends: './apps/server/.eslintrc.json',
234-
},
235-
],
219+
await expect(nxProjectsToConfig(projectGraph)).resolves.toEqual([
220+
{
221+
patterns: expect.arrayContaining([
222+
'apps/client/**/*.ts',
223+
'apps/client/**/*.html',
224+
]),
225+
eslintrc: './apps/client/.eslintrc.json',
236226
},
237-
patterns: expect.arrayContaining([
238-
'apps/client/**/*.ts',
239-
'apps/client/**/*.html',
240-
'apps/server/**/*.ts',
241-
]),
242-
} satisfies ESLintPluginConfig);
227+
{
228+
patterns: expect.arrayContaining(['apps/server/**/*.ts']),
229+
eslintrc: './apps/server/.eslintrc.json',
230+
},
231+
] satisfies ESLintPluginConfig);
243232
});
244233
});

0 commit comments

Comments
 (0)