Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1209ed2
feat: add artifaction options to plugin-eslint
Jul 27, 2025
8b001ae
Merge branch 'main' into feat/add-artefact-opts-to-eslint-plugin
BioPhoton Aug 27, 2025
4e0ffa3
Merge branch 'main' into feat/add-artefact-opts-to-eslint-plugin
BioPhoton Aug 27, 2025
687bada
feat(plugin-eslint): use artifacts options
BioPhoton Aug 27, 2025
a1732d4
refactor(plugin-eslint): fix int tests
BioPhoton Aug 27, 2025
5d24b82
refactor: remove unused code
BioPhoton Aug 28, 2025
5454e03
refactor: fix int tests 1
BioPhoton Aug 28, 2025
3695d7d
Merge branch 'main' into feat/add-artefact-opts-to-eslint-plugin
BioPhoton Aug 28, 2025
b71d211
refactor: fix int tests 2
BioPhoton Aug 28, 2025
cb1dbbc
refactor: fix lint 1
BioPhoton Aug 28, 2025
1757b7d
refactor: fix int test 4
BioPhoton Aug 28, 2025
f00a6d7
refactor: fix int test 6
BioPhoton Aug 28, 2025
11ca96e
Update packages/plugin-eslint/src/lib/runner.int.test.ts
BioPhoton Aug 29, 2025
33af94d
Update packages/plugin-eslint/src/lib/runner.int.test.ts
BioPhoton Aug 29, 2025
c1951f1
Update packages/plugin-eslint/src/lib/runner/index.ts
BioPhoton Aug 29, 2025
b55b1ca
Update packages/plugin-eslint/src/lib/runner/index.unit.test.ts
BioPhoton Aug 29, 2025
cd996fd
Update packages/plugin-eslint/src/lib/runner/index.unit.test.ts
BioPhoton Aug 29, 2025
f838cfb
Update packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts
BioPhoton Aug 29, 2025
67c2794
Update packages/plugin-eslint/src/lib/runner.int.test.ts
BioPhoton Aug 29, 2025
51a431e
refactor: improve tests
BioPhoton Aug 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/plugin-eslint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"glob": "^11.0.0",
"@code-pushup/utils": "0.77.0",
"@code-pushup/models": "0.77.0",
"yargs": "^17.7.2",
"zod": "^4.0.5"
},
"peerDependencies": {
Expand Down
7 changes: 0 additions & 7 deletions packages/plugin-eslint/src/bin.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -346,16 +346,7 @@ exports[`eslintPlugin > should initialize ESLint plugin for React application 1`
],
"icon": "eslint",
"packageName": "@code-pushup/eslint-plugin",
"runner": {
"args": [
""<dirname>/bin.js"",
"--runnerConfigPath="node_modules/.code-pushup/eslint/<timestamp>/plugin-config.json"",
"--runnerOutputPath="node_modules/.code-pushup/eslint/<timestamp>/runner-output.json"",
],
"command": "node",
"configFile": "node_modules/.code-pushup/eslint/<timestamp>/plugin-config.json",
"outputFile": "node_modules/.code-pushup/eslint/<timestamp>/runner-output.json",
},
"runner": [Function],
"slug": "eslint",
"title": "ESLint",
"version": Any<String>,
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-eslint/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { pluginArtifactOptionsSchema } from '@code-pushup/models';
import { toArray } from '@code-pushup/utils';

const patternsSchema = z
Expand Down Expand Up @@ -61,5 +62,6 @@ export type CustomGroup = z.infer<typeof customGroupSchema>;

export const eslintPluginOptionsSchema = z.object({
groups: z.array(customGroupSchema).optional(),
artifacts: pluginArtifactOptionsSchema.optional(),
});
export type ESLintPluginOptions = z.infer<typeof eslintPluginOptionsSchema>;
69 changes: 32 additions & 37 deletions packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import type { MockInstance } from 'vitest';
import type { Audit, PluginConfig, RunnerConfig } from '@code-pushup/models';
import { toUnixPath } from '@code-pushup/utils';
import type { Audit } from '@code-pushup/models';
import { eslintPlugin } from './eslint-plugin.js';

describe('eslintPlugin', () => {
Expand All @@ -15,27 +14,6 @@ describe('eslintPlugin', () => {
let cwdSpy: MockInstance<[], string>;
let platformSpy: MockInstance<[], NodeJS.Platform>;

const replaceAbsolutePath = (plugin: PluginConfig): PluginConfig => ({
...plugin,
runner: {
...(plugin.runner as RunnerConfig),
args: (plugin.runner as RunnerConfig).args?.map(arg =>
toUnixPath(arg.replace(path.dirname(thisDir), '<dirname>')).replace(
/\/eslint\/\d+\//,
'/eslint/<timestamp>/',
),
),
...((plugin.runner as RunnerConfig).configFile && {
configFile: toUnixPath(
(plugin.runner as RunnerConfig).configFile!,
).replace(/\/eslint\/\d+\//, '/eslint/<timestamp>/'),
}),
outputFile: toUnixPath(
(plugin.runner as RunnerConfig).outputFile,
).replace(/\/eslint\/\d+\//, '/eslint/<timestamp>/'),
},
});

beforeAll(() => {
cwdSpy = vi.spyOn(process, 'cwd');
// Linux produces extra quotation marks for globs
Expand All @@ -55,7 +33,7 @@ describe('eslintPlugin', () => {
patterns: ['src/**/*.js', 'src/**/*.jsx'],
});

expect(replaceAbsolutePath(plugin)).toMatchSnapshot({
expect(plugin).toMatchSnapshot({
version: expect.any(String),
});
});
Expand All @@ -68,18 +46,17 @@ describe('eslintPlugin', () => {
});

// expect rule from extended base eslint.config.js
expect(plugin.audits).toContainEqual(
expect.objectContaining<Audit>({
slug: expect.stringMatching(/^nx-enforce-module-boundaries/),
title: expect.any(String),
description: expect.stringContaining('sourceTag'),
}),
);
// expect rule from nx-plugin project's eslint.config.js
expect(plugin.audits).toContainEqual(
expect.objectContaining<Partial<Audit>>({
slug: 'nx-nx-plugin-checks',
}),
expect(plugin.audits).toStrictEqual(
expect.arrayContaining([
expect.objectContaining<Audit>({
slug: expect.stringMatching(/^nx-enforce-module-boundaries/),
title: expect.any(String),
description: expect.stringContaining('sourceTag'),
}),
expect.objectContaining<Partial<Audit>>({
slug: 'nx-nx-plugin-checks',
}),
]),
);
});

Expand Down Expand Up @@ -152,12 +129,30 @@ describe('eslintPlugin', () => {
await expect(
// @ts-expect-error simulating invalid non-TS config
eslintPlugin({ eslintrc: '.eslintrc.json' }),
).rejects.toThrow('Invalid input');
).rejects.toThrow('Failed parsing ESLint plugin config');
});

it("should throw if eslintrc file doesn't exist", async () => {
await expect(
eslintPlugin({ eslintrc: '.eslintrc.yml', patterns: '**/*.js' }),
).rejects.toThrow(/Failed to load url .*\.eslintrc.yml/);
});

it('should initialize with artifact options', async () => {
cwdSpy.mockReturnValue(path.join(fixturesDir, 'todos-app'));
const plugin = await eslintPlugin(
{
eslintrc: 'eslint.config.js',
patterns: ['src/**/*.js'],
},
{
artifacts: {
artifactsPaths: './artifacts/eslint-output.json',
generateArtifactsCommand: 'echo "Generating artifacts"',
},
},
);

expect(plugin.runner).toBeTypeOf('function');
});
});
22 changes: 9 additions & 13 deletions packages/plugin-eslint/src/lib/eslint-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { PluginConfig } from '@code-pushup/models';
import { parseSchema } from '@code-pushup/utils';
import {
Expand All @@ -11,7 +9,7 @@ import {
} from './config.js';
import { ESLINT_PLUGIN_SLUG } from './constants.js';
import { listAuditsAndGroups } from './meta/index.js';
import { createRunnerConfig } from './runner/index.js';
import { createRunnerFunction } from './runner/index.js';

/**
* Instantiates Code PushUp ESLint plugin for use in core config.
Expand Down Expand Up @@ -42,20 +40,14 @@ export async function eslintPlugin(
schemaType: 'ESLint plugin config',
});

const customGroups = options
const { groups: customGroups, artifacts } = options
? parseSchema(eslintPluginOptionsSchema, options, {
schemaType: 'ESLint plugin options',
}).groups
: undefined;
})
: {};

const { audits, groups } = await listAuditsAndGroups(targets, customGroups);

const runnerScriptPath = path.join(
fileURLToPath(path.dirname(import.meta.url)),
'..',
'bin.js',
);

const packageJson = createRequire(import.meta.url)(
'../../package.json',
) as typeof import('../../package.json');
Expand All @@ -72,6 +64,10 @@ export async function eslintPlugin(
audits,
groups,

runner: await createRunnerConfig(runnerScriptPath, audits, targets),
runner: await createRunnerFunction({
audits,
targets,
...(artifacts ? { artifacts } : {}),
}),
};
}
51 changes: 21 additions & 30 deletions packages/plugin-eslint/src/lib/runner.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,29 @@ import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { type MockInstance, describe, expect, it } from 'vitest';
import type {
AuditOutput,
AuditOutputs,
Issue,
RunnerFilesPaths,
import {
type Audit,
type AuditOutput,
type AuditOutputs,
DEFAULT_PERSIST_OUTPUT_DIR,
type Issue,
} from '@code-pushup/models';
import { osAgnosticAuditOutputs } from '@code-pushup/test-utils';
import { readJsonFile } from '@code-pushup/utils';
import type { ESLintTarget } from './config.js';
import { listAuditsAndGroups } from './meta/index.js';
import { createRunnerConfig, executeRunner } from './runner/index.js';
import { createRunnerFunction } from './runner/index.js';

describe('executeRunner', () => {
let cwdSpy: MockInstance<[], string>;
let platformSpy: MockInstance<[], NodeJS.Platform>;

const createPluginConfig = async (
const prepareRunnerArgs = async (
eslintrc: ESLintTarget['eslintrc'],
): Promise<RunnerFilesPaths> => {
): Promise<{ audits: Audit[]; targets: ESLintTarget[] }> => {
const patterns = ['src/**/*.js', 'src/**/*.jsx'];
const targets: ESLintTarget[] = [{ eslintrc, patterns }];
const { audits } = await listAuditsAndGroups(targets);
const { outputFile, configFile } = await createRunnerConfig(
'bin.js',
audits,
targets,
);
return {
runnerOutputPath: outputFile,
runnerConfigPath: configFile!,
};
return { audits, targets };
};

const appDir = path.join(
Expand All @@ -57,24 +49,23 @@ describe('executeRunner', () => {
});

it('should execute ESLint and create audit results for React application', async () => {
const runnerPaths = await createPluginConfig('eslint.config.js');
await executeRunner(runnerPaths);

const json = await readJsonFile<AuditOutputs>(runnerPaths.runnerOutputPath);
expect(osAgnosticAuditOutputs(json)).toMatchSnapshot();
const args = await prepareRunnerArgs('eslint.config.js');
const runnerFn = await createRunnerFunction(args);
const res = (await runnerFn({
outputDir: DEFAULT_PERSIST_OUTPUT_DIR,
})) as AuditOutputs;
expect(osAgnosticAuditOutputs(res)).toMatchSnapshot();
});

it.skipIf(process.platform === 'win32')(
'should execute runner with custom config using @code-pushup/eslint-config',
async () => {
const runnerPaths = await createPluginConfig(
'code-pushup.eslint.config.mjs',
);
await executeRunner(runnerPaths);
const eslintTarget = 'code-pushup.eslint.config.mjs';
const runnerFn = await createRunnerFunction({
...(await prepareRunnerArgs(eslintTarget)),
});

const json = await readJsonFile<AuditOutput[]>(
runnerPaths.runnerOutputPath,
);
const json = await runnerFn({ outputDir: DEFAULT_PERSIST_OUTPUT_DIR });
// expect warnings from unicorn/filename-case rule from default config
expect(json).toContainEqual(
expect.objectContaining<Partial<AuditOutput>>({
Expand Down
Loading
Loading