Skip to content

Commit e4611f6

Browse files
committed
refactor(plugin-coverage): improve config-file lcov handling
1 parent 6876789 commit e4611f6

File tree

2 files changed

+65
-56
lines changed

2 files changed

+65
-56
lines changed

packages/plugin-coverage/src/lib/config-file.ts

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,64 @@
11
import { generateCode, parseModule } from 'magicast';
2-
import { deepMergeObject } from 'magicast/helpers';
2+
import { deepMergeObject, getDefaultExportOptions } from 'magicast/helpers';
33

4-
type ProxyObject = Record<string, unknown> & {
5-
toJSON?: () => unknown;
6-
};
7-
8-
const REPORTER_CONFIGS: Record<string, { path: string[]; key: string }> = {
9-
vitest: { path: ['test', 'coverage'], key: 'reporter' },
10-
jest: { path: [], key: 'coverageReporters' },
11-
};
4+
const VITEST_DEFAULTS = ['text', 'html', 'clover', 'json'];
125

136
export function hasLcovReporter(content: string, framework: string): boolean {
14-
const reporterConfig = REPORTER_CONFIGS[framework];
15-
if (!reporterConfig) {
16-
return false;
7+
switch (framework) {
8+
case 'vitest':
9+
return /['"]lcov['"]/.test(content) && content.includes('reporter');
10+
case 'jest':
11+
return (
12+
!content.includes('coverageReporters') || /['"]lcov['"]/.test(content)
13+
);
14+
default:
15+
return false;
1716
}
18-
return /['"]lcov['"]/.test(content) && content.includes(reporterConfig.key);
1917
}
2018

2119
export function addLcovReporter(content: string, framework: string): string {
22-
const reporterConfig = REPORTER_CONFIGS[framework];
23-
if (!reporterConfig) {
24-
return content;
20+
switch (framework) {
21+
case 'vitest':
22+
return addLcovToVitest(content);
23+
case 'jest':
24+
return addLcovToJest(content);
25+
default:
26+
return content;
2527
}
28+
}
29+
30+
function addLcovToVitest(content: string): string {
2631
try {
2732
const mod = parseModule(content);
28-
const exported = mod.exports['default'];
29-
const configObject =
30-
exported.$type === 'function-call' ? exported.$args[0] : exported;
31-
const currentReporters = readReporters(configObject, reporterConfig);
32-
const updatedReporters = [...currentReporters, 'lcov'];
33-
33+
const config = getDefaultExportOptions(mod);
34+
const reporter = config['test']?.['coverage']?.['reporter'];
35+
const base = reporter?.['length'] ? [...reporter] : VITEST_DEFAULTS;
3436
deepMergeObject(
35-
configObject,
36-
buildNestedObject(
37-
[...reporterConfig.path, reporterConfig.key],
38-
updatedReporters,
39-
),
37+
config,
38+
buildNestedObject(['test', 'coverage', 'reporter'], [...base, 'lcov']),
4039
);
41-
4240
return generateCode(mod).code;
4341
} catch {
4442
return content;
4543
}
4644
}
4745

48-
function isProxyObject(value: unknown): value is ProxyObject {
49-
return typeof value === 'object' && value != null;
50-
}
51-
52-
function readReporters(
53-
configObject: ProxyObject,
54-
{ path, key }: { path: string[]; key: string },
55-
): string[] {
56-
const container = path.reduce<ProxyObject>((parent, segment) => {
57-
const nested = parent[segment];
58-
return isProxyObject(nested) ? nested : {};
59-
}, configObject);
60-
const reporterProxy = container[key];
61-
const resolved =
62-
isProxyObject(reporterProxy) && typeof reporterProxy.toJSON === 'function'
63-
? reporterProxy.toJSON()
64-
: reporterProxy;
65-
return Array.isArray(resolved) ? resolved : [];
46+
function addLcovToJest(content: string): string {
47+
try {
48+
const mod = parseModule(content);
49+
const config = getDefaultExportOptions(mod);
50+
const reporters = config['coverageReporters'];
51+
if (!reporters?.['length']) {
52+
return content;
53+
}
54+
deepMergeObject(
55+
config,
56+
buildNestedObject(['coverageReporters'], [...reporters, 'lcov']),
57+
);
58+
return generateCode(mod).code;
59+
} catch {
60+
return content;
61+
}
6662
}
6763

6864
export function buildNestedObject(

packages/plugin-coverage/src/lib/config-file.unit.test.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,29 @@ import {
55
} from './config-file.js';
66

77
describe('hasLcovReporter', () => {
8-
it('should detect lcov in vitest config', () => {
8+
it('should return true for vitest reporter with lcov', () => {
99
expect(hasLcovReporter("reporter: ['text', 'lcov']", 'vitest')).toBeTrue();
1010
});
1111

12-
it('should return false when lcov is absent', () => {
12+
it('should return false for vitest reporter without lcov', () => {
13+
expect(hasLcovReporter("reporter: ['text']", 'vitest')).toBeFalse();
14+
});
15+
16+
it('should return false for vitest without reporter key', () => {
17+
expect(hasLcovReporter('globals: true', 'vitest')).toBeFalse();
18+
});
19+
20+
it('should return true for jest without coverageReporters (lcov is default)', () => {
21+
expect(hasLcovReporter('testEnvironment: "node"', 'jest')).toBeTrue();
22+
});
23+
24+
it('should return true for jest coverageReporters with lcov', () => {
25+
expect(
26+
hasLcovReporter("coverageReporters: ['text', 'lcov']", 'jest'),
27+
).toBeTrue();
28+
});
29+
30+
it('should return false for jest coverageReporters without lcov', () => {
1331
expect(hasLcovReporter("coverageReporters: ['text']", 'jest')).toBeFalse();
1432
});
1533
});
@@ -56,7 +74,7 @@ export default defineConfig({
5674
globals: true,
5775
5876
coverage: {
59-
reporter: ['lcov'],
77+
reporter: ['text', 'html', 'clover', 'json', 'lcov'],
6078
},
6179
},
6280
});"
@@ -75,17 +93,12 @@ export default defineConfig({
7593
`);
7694
});
7795

78-
it('should add coverageReporters to jest config when missing', () => {
96+
it('should not modify jest config when coverageReporters is missing (lcov enabled by default)', () => {
7997
const input = `export default {
8098
testEnvironment: 'node',
8199
};
82100
`;
83-
expect(addLcovReporter(input, 'jest')).toMatchInlineSnapshot(`
84-
"export default {
85-
testEnvironment: 'node',
86-
coverageReporters: ['lcov'],
87-
};"
88-
`);
101+
expect(addLcovReporter(input, 'jest')).toBe(input);
89102
});
90103

91104
it('should return CJS config unchanged', () => {

0 commit comments

Comments
 (0)