Skip to content

Commit

Permalink
feat(plugin-eslint): nx helpers generate array of lint targets
Browse files Browse the repository at this point in the history
  • Loading branch information
matejchalk committed Apr 29, 2024
1 parent 7b1e458 commit 10dd3c6
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 154 deletions.
130 changes: 53 additions & 77 deletions packages/plugin-eslint/src/lib/nx.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { setWorkspaceRoot, workspaceRoot } from 'nx/src/utils/workspace-root';
import type { MockInstance } from 'vitest';
import { ESLintPluginConfig } from './config';
import { type ESLintTarget } from './config';
import { eslintConfigFromNxProject, eslintConfigFromNxProjects } from './nx';

describe('Nx helpers', () => {
Expand Down Expand Up @@ -33,63 +33,53 @@ describe('Nx helpers', () => {

describe('create config from all Nx projects', () => {
it('should include eslintrc and patterns of each project', async () => {
await expect(eslintConfigFromNxProjects()).resolves.toEqual({
eslintrc: {
root: true,
overrides: [
{
files: ['packages/cli/**/*.ts', 'packages/cli/package.json'],
extends: './packages/cli/.eslintrc.json',
},
{
files: ['packages/core/**/*.ts', 'packages/core/package.json'],
extends: './packages/core/.eslintrc.json',
},
{
files: [
'packages/nx-plugin/**/*.ts',
'packages/nx-plugin/package.json',
'packages/nx-plugin/generators.json',
],
extends: './packages/nx-plugin/.eslintrc.json',
},
{
files: ['packages/utils/**/*.ts', 'packages/utils/package.json'],
extends: './packages/utils/.eslintrc.json',
},
await expect(eslintConfigFromNxProjects()).resolves.toEqual([
{
eslintrc: './packages/cli/.eslintrc.json',
patterns: [
'packages/cli/**/*.ts',
'packages/cli/package.json',
'packages/cli/src/*.spec.ts',
'packages/cli/src/*.cy.ts',
'packages/cli/src/*.stories.ts',
'packages/cli/src/.storybook/main.ts',
],
},
patterns: [
'packages/cli/**/*.ts',
'packages/cli/package.json',
'packages/cli/src/*.spec.ts',
'packages/cli/src/*.cy.ts',
'packages/cli/src/*.stories.ts',
'packages/cli/src/.storybook/main.ts',

'packages/core/**/*.ts',
'packages/core/package.json',
'packages/core/src/*.spec.ts',
'packages/core/src/*.cy.ts',
'packages/core/src/*.stories.ts',
'packages/core/src/.storybook/main.ts',

'packages/nx-plugin/**/*.ts',
'packages/nx-plugin/package.json',
'packages/nx-plugin/generators.json',
'packages/nx-plugin/src/*.spec.ts',
'packages/nx-plugin/src/*.cy.ts',
'packages/nx-plugin/src/*.stories.ts',
'packages/nx-plugin/src/.storybook/main.ts',

'packages/utils/**/*.ts',
'packages/utils/package.json',
'packages/utils/src/*.spec.ts',
'packages/utils/src/*.cy.ts',
'packages/utils/src/*.stories.ts',
'packages/utils/src/.storybook/main.ts',
],
} satisfies ESLintPluginConfig);
{
eslintrc: './packages/core/.eslintrc.json',
patterns: [
'packages/core/**/*.ts',
'packages/core/package.json',
'packages/core/src/*.spec.ts',
'packages/core/src/*.cy.ts',
'packages/core/src/*.stories.ts',
'packages/core/src/.storybook/main.ts',
],
},
{
eslintrc: './packages/nx-plugin/.eslintrc.json',
patterns: [
'packages/nx-plugin/**/*.ts',
'packages/nx-plugin/package.json',
'packages/nx-plugin/generators.json',
'packages/nx-plugin/src/*.spec.ts',
'packages/nx-plugin/src/*.cy.ts',
'packages/nx-plugin/src/*.stories.ts',
'packages/nx-plugin/src/.storybook/main.ts',
],
},
{
eslintrc: './packages/utils/.eslintrc.json',
patterns: [
'packages/utils/**/*.ts',
'packages/utils/package.json',
'packages/utils/src/*.spec.ts',
'packages/utils/src/*.cy.ts',
'packages/utils/src/*.stories.ts',
'packages/utils/src/.storybook/main.ts',
],
},
] satisfies ESLintTarget[]);
});
});

Expand Down Expand Up @@ -119,28 +109,14 @@ describe('Nx helpers', () => {
])(
'project %j - expected configurations for projects %j',
async (project, expectedProjects) => {
const otherProjects = ALL_PROJECTS.filter(
p => !expectedProjects.includes(p),
);

const config = await eslintConfigFromNxProject(project);
const targets = await eslintConfigFromNxProject(project);

expect(config.eslintrc).toEqual({
root: true,
overrides: expectedProjects.map(p => ({
files: expect.arrayContaining([`packages/${p}/**/*.ts`]),
extends: `./packages/${p}/.eslintrc.json`,
})),
});

expect(config.patterns).toEqual(
expect.arrayContaining(
expectedProjects.map(p => `packages/${p}/**/*.ts`),
),
);
expect(config.patterns).toEqual(
expect.not.arrayContaining(
otherProjects.map(p => `packages/${p}/**/*.ts`),
expect(targets).toEqual(
expectedProjects.map(
(p): ESLintTarget => ({
eslintrc: `./packages/${p}/.eslintrc.json`,
patterns: expect.arrayContaining([`packages/${p}/**/*.ts`]),
}),
),
);
},
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-eslint/src/lib/nx/find-all-projects.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ESLintPluginConfig } from '../config';
import type { ESLintTarget } from '../config';
import { nxProjectsToConfig } from './projects-to-config';

/**
Expand All @@ -22,7 +22,7 @@ import { nxProjectsToConfig } from './projects-to-config';
*
* @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin}
*/
export async function eslintConfigFromNxProjects(): Promise<ESLintPluginConfig> {
export async function eslintConfigFromNxProjects(): Promise<ESLintTarget[]> {
const { createProjectGraphAsync } = await import('@nx/devkit');
const projectGraph = await createProjectGraphAsync({ exitOnError: false });
return nxProjectsToConfig(projectGraph);
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-eslint/src/lib/nx/find-project-with-deps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ESLintPluginConfig } from '../config';
import type { ESLintTarget } from '../config';
import { nxProjectsToConfig } from './projects-to-config';
import { findAllDependencies } from './traverse-graph';

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

Expand Down
47 changes: 18 additions & 29 deletions packages/plugin-eslint/src/lib/nx/projects-to-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ProjectConfiguration, ProjectGraph } from '@nx/devkit';
import type { ESLint } from 'eslint';
import type { ESLintPluginConfig } from '../config';
import type { ESLintTarget } from '../config';
import {
findCodePushupEslintrc,
getEslintConfig,
Expand All @@ -10,7 +9,7 @@ import {
export async function nxProjectsToConfig(
projectGraph: ProjectGraph,
predicate: (project: ProjectConfiguration) => boolean = () => true,
): Promise<ESLintPluginConfig> {
): Promise<ESLintTarget[]> {
// find Nx projects with lint target
const { readProjectsConfigurationFromProjectGraph } = await import(
'@nx/devkit'
Expand All @@ -22,32 +21,22 @@ export async function nxProjectsToConfig(
.filter(predicate) // apply predicate
.sort((a, b) => a.root.localeCompare(b.root));

// create single ESLint config with project-specific overrides
const eslintConfig: ESLint.ConfigData = {
root: true,
overrides: await Promise.all(
projects.map(async project => ({
files: getLintFilePatterns(project),
extends:
return Promise.all(
projects.map(
async (project): Promise<ESLintTarget> => ({
eslintrc:
(await findCodePushupEslintrc(project)) ?? getEslintConfig(project),
})),
patterns: [
...getLintFilePatterns(project),
// HACK: ESLint.calculateConfigForFile won't find rules included only for subsets of *.ts when globs used
// so we explicitly provide additional patterns used by @code-pushup/eslint-config to ensure those rules are included
// this workaround won't be necessary once flat configs are stable (much easier to find all rules)
`${project.sourceRoot}/*.spec.ts`, // jest/* and vitest/* rules
`${project.sourceRoot}/*.cy.ts`, // cypress/* rules
`${project.sourceRoot}/*.stories.ts`, // storybook/* rules
`${project.sourceRoot}/.storybook/main.ts`, // storybook/no-uninstalled-addons rule
],
}),
),
};

// include patterns from each project
const patterns = projects.flatMap(project => [
...getLintFilePatterns(project),
// HACK: ESLint.calculateConfigForFile won't find rules included only for subsets of *.ts when globs used
// so we explicitly provide additional patterns used by @code-pushup/eslint-config to ensure those rules are included
// this workaround won't be necessary once flat configs are stable (much easier to find all rules)
`${project.sourceRoot}/*.spec.ts`, // jest/* and vitest/* rules
`${project.sourceRoot}/*.cy.ts`, // cypress/* rules
`${project.sourceRoot}/*.stories.ts`, // storybook/* rules
`${project.sourceRoot}/.storybook/main.ts`, // storybook/no-uninstalled-addons rule
]);

return {
eslintrc: eslintConfig,
patterns,
};
);
}
77 changes: 33 additions & 44 deletions packages/plugin-eslint/src/lib/nx/projects-to-config.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import type {
ProjectGraphDependency,
ProjectGraphProjectNode,
} from '@nx/devkit';
import type { ESLint } from 'eslint';
import { vol } from 'memfs';
import type { MockInstance } from 'vitest';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import type { ESLintPluginConfig } from '../config';
import type { ESLintPluginConfig, ESLintTarget } from '../config';
import { nxProjectsToConfig } from './projects-to-config';

describe('nxProjectsToConfig', () => {
Expand All @@ -28,6 +27,7 @@ describe('nxProjectsToConfig', () => {
},
},
},
sourceRoot: `${node.data.root}/src`,
...node.data,
},
},
Expand Down Expand Up @@ -65,20 +65,19 @@ describe('nxProjectsToConfig', () => {
]);

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

expect(overrides).toEqual([
expect(config).toEqual<ESLintPluginConfig>([
{
files: ['apps/client/**/*.ts'],
extends: './apps/client/.eslintrc.json',
eslintrc: './apps/client/.eslintrc.json',
patterns: expect.arrayContaining(['apps/client/**/*.ts']),
},
{
files: ['apps/server/**/*.ts'],
extends: './apps/server/.eslintrc.json',
eslintrc: './apps/server/.eslintrc.json',
patterns: expect.arrayContaining(['apps/server/**/*.ts']),
},
{
files: ['libs/models/**/*.ts'],
extends: './libs/models/.eslintrc.json',
eslintrc: './libs/models/.eslintrc.json',
patterns: expect.arrayContaining(['libs/models/**/*.ts']),
},
]);
});
Expand Down Expand Up @@ -107,11 +106,10 @@ describe('nxProjectsToConfig', () => {
project => project.projectType === 'library',
);

const { overrides } = config.eslintrc as ESLint.ConfigData;
expect(overrides).toEqual([
expect(config).toEqual<ESLintPluginConfig>([
{
files: ['libs/models/**/*.ts'],
extends: './libs/models/.eslintrc.json',
eslintrc: './libs/models/.eslintrc.json',
patterns: expect.arrayContaining(['libs/models/**/*.ts']),
},
]);
});
Expand Down Expand Up @@ -150,15 +148,14 @@ describe('nxProjectsToConfig', () => {

const config = await nxProjectsToConfig(projectGraph);

const { overrides } = config.eslintrc as ESLint.ConfigData;
expect(overrides).toEqual([
expect(config).toEqual<ESLintPluginConfig>([
{
files: ['apps/client/**/*.ts'],
extends: './apps/client/.eslintrc.json',
eslintrc: './apps/client/.eslintrc.json',
patterns: expect.arrayContaining(['apps/client/**/*.ts']),
},
{
files: ['apps/server/**/*.ts'],
extends: './apps/server/.eslintrc.json',
eslintrc: './apps/server/.eslintrc.json',
patterns: expect.arrayContaining(['apps/server/**/*.ts']),
},
]);
});
Expand All @@ -167,7 +164,7 @@ describe('nxProjectsToConfig', () => {
vol.fromJSON(
{
'apps/client/code-pushup.eslintrc.json':
'{ "extends": "@code-pushup" }',
'{ "eslintrc": "@code-pushup" }',
},
MEMFS_VOLUME,
);
Expand All @@ -177,10 +174,9 @@ describe('nxProjectsToConfig', () => {

const config = await nxProjectsToConfig(projectGraph);

const { overrides } = config.eslintrc as ESLint.ConfigData;
expect(overrides).toEqual([
expect.objectContaining({
extends: './apps/client/code-pushup.eslintrc.json',
expect(config).toEqual([
expect.objectContaining<Partial<ESLintTarget>>({
eslintrc: './apps/client/code-pushup.eslintrc.json',
}),
]);
});
Expand Down Expand Up @@ -220,25 +216,18 @@ describe('nxProjectsToConfig', () => {
},
]);

await expect(nxProjectsToConfig(projectGraph)).resolves.toEqual({
eslintrc: {
root: true,
overrides: [
{
files: ['apps/client/**/*.ts', 'apps/client/**/*.html'],
extends: './apps/client/.eslintrc.json',
},
{
files: ['apps/server/**/*.ts'],
extends: './apps/server/.eslintrc.json',
},
],
await expect(nxProjectsToConfig(projectGraph)).resolves.toEqual([
{
patterns: expect.arrayContaining([
'apps/client/**/*.ts',
'apps/client/**/*.html',
]),
eslintrc: './apps/client/.eslintrc.json',
},
patterns: expect.arrayContaining([
'apps/client/**/*.ts',
'apps/client/**/*.html',
'apps/server/**/*.ts',
]),
} satisfies ESLintPluginConfig);
{
patterns: expect.arrayContaining(['apps/server/**/*.ts']),
eslintrc: './apps/server/.eslintrc.json',
},
] satisfies ESLintPluginConfig);
});
});

0 comments on commit 10dd3c6

Please sign in to comment.