Skip to content

Commit 9c9b3ac

Browse files
committed
feat(plugin-eslint): log runner steps (lint results stats, artifacts usage)
1 parent 64d40a1 commit 9c9b3ac

File tree

4 files changed

+138
-8
lines changed

4 files changed

+138
-8
lines changed

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import type {
55
PluginArtifactOptions,
66
RunnerFunction,
77
} from '@code-pushup/models';
8-
import { asyncSequential, logger } from '@code-pushup/utils';
8+
import {
9+
asyncSequential,
10+
logger,
11+
pluralizeToken,
12+
roundDecimals,
13+
} from '@code-pushup/utils';
914
import type { ESLintPluginRunnerConfig, ESLintTarget } from '../config.js';
1015
import { lint } from './lint.js';
16+
import { aggregateLintResultsStats } from './stats.js';
1117
import { lintResultsToAudits, mergeLinterOutputs } from './transform.js';
1218
import { loadArtifacts } from './utils.js';
1319

@@ -23,14 +29,32 @@ export function createRunnerFunction(options: {
2329
};
2430

2531
return async (): Promise<AuditOutputs> => {
26-
logger.info(`ESLint plugin executing ${targets.length} lint targets`);
32+
logger.info(
33+
`ESLint plugin executing ${pluralizeToken('lint target', targets.length)}`,
34+
);
2735

2836
const linterOutputs = artifacts
2937
? await loadArtifacts(artifacts)
3038
: await asyncSequential(targets, lint);
39+
3140
const lintResults = mergeLinterOutputs(linterOutputs);
3241
const failedAudits = lintResultsToAudits(lintResults);
3342

43+
const stats = aggregateLintResultsStats(lintResults.results);
44+
logger.info(
45+
stats.problemsCount === 0
46+
? 'ESLint did not find any problems'
47+
: `ESLint found ${pluralizeToken('problem', stats.problemsCount)} from ${pluralizeToken('rule', stats.failedRulesCount)} across ${pluralizeToken('file', stats.failedFilesCount)}`,
48+
);
49+
50+
const totalCount = config.slugs.length;
51+
const failedCount = failedAudits.length;
52+
const passedCount = totalCount - failedCount;
53+
const percentage = roundDecimals((passedCount / totalCount) * 100, 2);
54+
logger.info(
55+
`${pluralizeToken('audit', passedCount)} passed, ${pluralizeToken('audit', failedCount)} failed (${percentage}% success)`,
56+
);
57+
3458
return config.slugs.map(
3559
(slug): AuditOutput =>
3660
failedAudits.find(audit => audit.slug === slug) ?? {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ESLint } from 'eslint';
2+
3+
export type LintResultsStats = {
4+
problemsCount: number;
5+
failedFilesCount: number;
6+
failedRulesCount: number;
7+
};
8+
9+
export function aggregateLintResultsStats(
10+
results: ESLint.LintResult[],
11+
): LintResultsStats {
12+
const problemsCount = results.reduce(
13+
(acc, result) => acc + result.messages.length,
14+
0,
15+
);
16+
const failedFilesCount = results.length;
17+
const failedRulesCount = new Set(
18+
results.flatMap(({ messages }) =>
19+
messages.map(({ ruleId }) => ruleId).filter(Boolean),
20+
),
21+
).size;
22+
return { problemsCount, failedFilesCount, failedRulesCount };
23+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { ESLint } from 'eslint';
2+
import { type LintResultsStats, aggregateLintResultsStats } from './stats.js';
3+
4+
describe('aggregateLintResultsStats', () => {
5+
it('should sum all errors and warning across all files', () => {
6+
expect(
7+
aggregateLintResultsStats([
8+
{
9+
filePath: 'src/main.js',
10+
messages: [{ severity: 2 }],
11+
},
12+
{
13+
filePath: 'src/lib/index.js',
14+
messages: [{ severity: 2 }, { severity: 1 }],
15+
},
16+
{
17+
filePath: 'src/lib/utils.js',
18+
messages: [
19+
{ severity: 1 },
20+
{ severity: 1 },
21+
{ severity: 2 },
22+
{ severity: 1 },
23+
],
24+
},
25+
] as ESLint.LintResult[]),
26+
).toEqual(
27+
expect.objectContaining<Partial<LintResultsStats>>({
28+
problemsCount: 7,
29+
}),
30+
);
31+
});
32+
33+
it('should count files with problems', () => {
34+
expect(
35+
aggregateLintResultsStats([
36+
{ filePath: 'src/main.js', messages: [{}] },
37+
{ filePath: 'src/lib/index.js', messages: [{}, {}] },
38+
{ filePath: 'src/lib/utils.js', messages: [{}, {}, {}] },
39+
] as ESLint.LintResult[]),
40+
).toEqual(
41+
expect.objectContaining<Partial<LintResultsStats>>({
42+
failedFilesCount: 3,
43+
}),
44+
);
45+
});
46+
47+
it('should count unique rules with reported problems', () => {
48+
expect(
49+
aggregateLintResultsStats([
50+
{ filePath: 'src/lib/main.js', messages: [{}] }, // empty ruleId ignored
51+
{
52+
filePath: 'src/lib/index.js',
53+
messages: [{ ruleId: 'max-lines' }, { ruleId: 'no-unused-vars' }],
54+
},
55+
{
56+
filePath: 'src/lib/utils.js',
57+
messages: [
58+
{ ruleId: 'no-unused-vars' },
59+
{ ruleId: 'eqeqeq' },
60+
{ ruleId: 'no-unused-vars' },
61+
{ ruleId: 'yoda' },
62+
{ ruleId: 'eqeqeq' },
63+
],
64+
},
65+
] as ESLint.LintResult[]),
66+
).toEqual(
67+
expect.objectContaining<Partial<LintResultsStats>>({
68+
failedRulesCount: 4, // no-unused-vars (3), eqeqeq (2), max-lines (1), yoda (1)
69+
}),
70+
);
71+
});
72+
});

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import type { ESLint } from 'eslint';
22
import { glob } from 'glob';
33
import type { PluginArtifactOptions } from '@code-pushup/models';
4-
import { executeProcess, readJsonFile } from '@code-pushup/utils';
4+
import {
5+
executeProcess,
6+
logger,
7+
pluralize,
8+
pluralizeToken,
9+
readJsonFile,
10+
toArray,
11+
} from '@code-pushup/utils';
512
import type { LinterOutput } from './types.js';
613

714
export async function loadArtifacts(
@@ -20,13 +27,11 @@ export async function loadArtifacts(
2027
});
2128
}
2229

23-
const initialArtifactPaths = Array.isArray(artifacts.artifactsPaths)
24-
? artifacts.artifactsPaths
25-
: [artifacts.artifactsPaths];
30+
const artifactPatterns = toArray(artifacts.artifactsPaths);
2631

27-
const artifactPaths = await glob(initialArtifactPaths);
32+
const artifactPaths = await glob(artifactPatterns);
2833

29-
return await Promise.all(
34+
const outputs = await Promise.all(
3035
artifactPaths.map(async artifactPath => {
3136
// ESLint CLI outputs raw ESLint.LintResult[], but we need LinterOutput format
3237
const results = await readJsonFile<ESLint.LintResult[]>(artifactPath);
@@ -36,4 +41,10 @@ export async function loadArtifacts(
3641
};
3742
}),
3843
);
44+
45+
logger.info(
46+
`Loaded lint outputs from ${pluralizeToken('artifact', artifactPaths.length)} matching ${pluralize('pattern', artifactPatterns.length)}: ${artifactPatterns.join(' ')}`,
47+
);
48+
49+
return outputs;
3950
}

0 commit comments

Comments
 (0)