Skip to content

Commit 67571eb

Browse files
committed
feat(plugin-eslint): add support for inline eslint config
1 parent 9755053 commit 67571eb

File tree

8 files changed

+96
-52
lines changed

8 files changed

+96
-52
lines changed

packages/plugin-eslint/src/lib/config.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import { z } from 'zod';
1+
import type { ESLint } from 'eslint';
2+
import { type ZodType, z } from 'zod';
23

34
export const eslintPluginConfigSchema = z.object({
4-
eslintrc: z.string({
5-
description: 'Path to .eslintrc.* config file',
6-
}),
5+
eslintrc: z.union(
6+
[
7+
z.string({ description: 'Path to ESLint config file' }),
8+
z.record(z.string(), z.unknown(), {
9+
description: 'ESLint config object',
10+
}) as ZodType<ESLint.ConfigData>,
11+
],
12+
{ description: 'ESLint config as file path or inline object' },
13+
),
714
patterns: z.union([z.string(), z.array(z.string()).min(1)], {
815
description:
9-
'Lint target files. May contain file paths, directory paths, or glob patterns',
16+
'Lint target files. May contain file paths, directory paths or glob patterns',
1017
}),
1118
});
1219

packages/plugin-eslint/src/lib/eslint-plugin.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { ESLint } from 'eslint';
1+
import { writeFile } from 'fs/promises';
22
import { dirname, join } from 'path';
33
import { fileURLToPath } from 'url';
44
import { PluginConfig } from '@code-pushup/models';
55
import { name, version } from '../../package.json';
66
import { ESLintPluginConfig, eslintPluginConfigSchema } from './config';
77
import { listAuditsAndGroups } from './meta';
8-
import { createRunnerConfig } from './runner';
8+
import { ESLINTRC_PATH, createRunnerConfig } from './runner';
9+
import { setupESLint } from './setup';
910

1011
/**
1112
* Instantiates Code PushUp ESLint plugin for use in core config.
@@ -32,13 +33,16 @@ export async function eslintPlugin(
3233
): Promise<PluginConfig> {
3334
const { eslintrc, patterns } = eslintPluginConfigSchema.parse(config);
3435

35-
const eslint = new ESLint({
36-
overrideConfigFile: eslintrc,
37-
useEslintrc: false,
38-
});
36+
const eslint = setupESLint(eslintrc);
3937

4038
const { audits, groups } = await listAuditsAndGroups(eslint, patterns);
4139

40+
// save inline config to file so runner can access it later
41+
if (typeof eslintrc !== 'string') {
42+
await writeFile(ESLINTRC_PATH, JSON.stringify(eslintrc));
43+
}
44+
const eslintrcPath = typeof eslintrc === 'string' ? eslintrc : ESLINTRC_PATH;
45+
4246
const runnerScriptPath = join(
4347
fileURLToPath(dirname(import.meta.url)),
4448
'bin.js',
@@ -56,6 +60,11 @@ export async function eslintPlugin(
5660
audits,
5761
groups,
5862

59-
runner: createRunnerConfig(runnerScriptPath, audits, eslintrc, patterns),
63+
runner: createRunnerConfig(
64+
runnerScriptPath,
65+
audits,
66+
eslintrcPath,
67+
patterns,
68+
),
6069
};
6170
}

packages/plugin-eslint/src/lib/runner/index.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { mkdir, writeFile } from 'fs/promises';
22
import { platform } from 'os';
3-
import { dirname } from 'path';
3+
import { dirname, join } from 'path';
44
import type { Audit, AuditOutput, RunnerConfig } from '@code-pushup/models';
5-
import { toArray } from '@code-pushup/utils';
5+
import { pluginWorkDir, readJsonFile, toArray } from '@code-pushup/utils';
66
import { lint } from './lint';
77
import { lintResultsToAudits } from './transform';
88

9-
export const RUNNER_OUTPUT_PATH =
10-
'node_modules/.code-pushup/eslint/runner-output.json';
9+
const WORKDIR = pluginWorkDir('eslint');
10+
export const RUNNER_OUTPUT_PATH = join(WORKDIR, 'runner-output.json');
11+
export const ESLINTRC_PATH = join(WORKDIR, '.eslintrc.json');
1112

1213
const AUDIT_SLUGS_SEP = ',';
1314

@@ -23,7 +24,12 @@ export async function executeRunner(argv = process.argv): Promise<void> {
2324
throw new Error('Invalid runner args - missing patterns argument');
2425
}
2526

26-
const lintResults = await lint(eslintrc, patterns);
27+
const lintResults = await lint({
28+
// if file created from inline object, provide inline to preserve relative links
29+
eslintrc:
30+
eslintrc === ESLINTRC_PATH ? await readJsonFile(eslintrc) : eslintrc,
31+
patterns,
32+
});
2733
const failedAudits = lintResultsToAudits(lintResults);
2834

2935
const audits = slugs.split(AUDIT_SLUGS_SEP).map(

packages/plugin-eslint/src/lib/runner/lint.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { ESLint, type Linter } from 'eslint';
1+
import type { Linter } from 'eslint';
22
import { distinct, toArray, toUnixPath } from '@code-pushup/utils';
3+
import { ESLintPluginConfig } from '../config';
4+
import { setupESLint } from '../setup';
35
import type { LintResult, LinterOutput, RuleOptionsPerFile } from './types';
46

5-
export async function lint(
6-
eslintrc: string,
7-
patterns: string[],
8-
): Promise<LinterOutput> {
9-
const eslint = new ESLint({
10-
overrideConfigFile: eslintrc,
11-
useEslintrc: false,
12-
errorOnUnmatchedPattern: false,
13-
});
7+
export async function lint({
8+
eslintrc,
9+
patterns,
10+
}: ESLintPluginConfig): Promise<LinterOutput> {
11+
const eslint = setupESLint(eslintrc);
1412

1513
const lintResults = await eslint.lintFiles(patterns);
1614
const results = lintResults.map(

packages/plugin-eslint/src/lib/runner/lint.unit.test.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ESLint, Linter } from 'eslint';
2+
import { ESLintPluginConfig } from '../config';
23
import { lint } from './lint';
34

45
class MockESLint {
@@ -50,12 +51,17 @@ vi.mock('eslint', () => ({
5051
}));
5152

5253
describe('lint', () => {
54+
const config: ESLintPluginConfig = {
55+
eslintrc: '.eslintrc.js',
56+
patterns: ['**/*.js'],
57+
};
58+
5359
beforeEach(() => {
5460
vi.clearAllMocks();
5561
});
5662

5763
it('should add relativeFilePath to each lint result', async () => {
58-
const { results } = await lint('.eslintrc.js', ['**/*.js']);
64+
const { results } = await lint(config);
5965
expect(results).toEqual([
6066
expect.objectContaining({ relativeFilePath: 'src/app/app.component.ts' }),
6167
expect.objectContaining({
@@ -68,7 +74,7 @@ describe('lint', () => {
6874
});
6975

7076
it('should get rule options for each file', async () => {
71-
const { ruleOptionsPerFile } = await lint('.eslintrc.js', ['**/*.js']);
77+
const { ruleOptionsPerFile } = await lint(config);
7278
expect(ruleOptionsPerFile).toEqual({
7379
'src/app/app.component.ts': {
7480
'max-lines': [500],
@@ -85,7 +91,7 @@ describe('lint', () => {
8591
});
8692

8793
it('should correctly use ESLint Node API', async () => {
88-
await lint('.eslintrc.js', ['**/*.js']);
94+
await lint(config);
8995
expect(ESLint).toHaveBeenCalledWith<ConstructorParameters<typeof ESLint>>({
9096
overrideConfigFile: '.eslintrc.js',
9197
useEslintrc: false,
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ESLint } from 'eslint';
2+
import { ESLintPluginConfig } from './config';
3+
4+
export function setupESLint(eslintrc: ESLintPluginConfig['eslintrc']) {
5+
return new ESLint({
6+
...(typeof eslintrc === 'string'
7+
? { overrideConfigFile: eslintrc }
8+
: { baseConfig: eslintrc }),
9+
useEslintrc: false,
10+
errorOnUnmatchedPattern: false,
11+
});
12+
}

packages/utils/src/index.ts

+23-22
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,25 @@ export {
77
executeProcess,
88
objectToCliArgs,
99
} from './lib/execute-process';
10-
export { git, getLatestCommit } from './lib/git';
10+
export {
11+
FileResult,
12+
MultipleFileResults,
13+
ensureDirectoryExists,
14+
importEsmModule,
15+
logMultipleFileResults,
16+
pluginWorkDir,
17+
readJsonFile,
18+
readTextFile,
19+
toUnixPath,
20+
} from './lib/file-system';
21+
export { getLatestCommit, git } from './lib/git';
22+
export { logMultipleResults } from './lib/log-results';
23+
export { NEW_LINE } from './lib/md';
1124
export { ProgressBar, getProgressBar } from './lib/progress';
25+
export {
26+
isPromiseFulfilledResult,
27+
isPromiseRejectedResult,
28+
} from './lib/promise-result';
1229
export {
1330
CODE_PUSHUP_DOMAIN,
1431
FOOTER_PREFIX,
@@ -23,28 +40,12 @@ export { reportToMd } from './lib/report-to-md';
2340
export { reportToStdout } from './lib/report-to-stdout';
2441
export { ScoredReport, scoreReport } from './lib/scoring';
2542
export {
26-
readJsonFile,
27-
readTextFile,
28-
toUnixPath,
29-
ensureDirectoryExists,
30-
FileResult,
31-
MultipleFileResults,
32-
logMultipleFileResults,
33-
importEsmModule,
34-
} from './lib/file-system';
35-
export { verboseUtils } from './lib/verbose-utils';
36-
export {
37-
pluralize,
38-
toArray,
39-
objectToKeys,
40-
objectToEntries,
4143
countOccurrences,
4244
distinct,
45+
objectToEntries,
46+
objectToKeys,
47+
pluralize,
4348
slugify,
49+
toArray,
4450
} from './lib/transformation';
45-
export { NEW_LINE } from './lib/md';
46-
export { logMultipleResults } from './lib/log-results';
47-
export {
48-
isPromiseFulfilledResult,
49-
isPromiseRejectedResult,
50-
} from './lib/promise-result';
51+
export { verboseUtils } from './lib/verbose-utils';

packages/utils/src/lib/file-system.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type Options, bundleRequire } from 'bundle-require';
22
import chalk from 'chalk';
33
import { mkdir, readFile } from 'fs/promises';
4+
import { join } from 'path';
45
import { logMultipleResults } from './log-results';
56
import { formatBytes } from './report';
67

@@ -87,3 +88,7 @@ export async function importEsmModule<T = unknown>(
8788
}
8889
return parse(mod.default);
8990
}
91+
92+
export function pluginWorkDir(slug: string): string {
93+
return join('node_modules', '.code-pushup', slug);
94+
}

0 commit comments

Comments
 (0)