1 | import fs from 'fs';
|
2 | import { join } from 'path';
|
3 | import { fileURLToPath } from 'url';
|
4 | import v8Coverage from '@bcoe/v8-coverage';
|
5 | import sourceRange from '../private/sourceRange.mjs';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | export default async function analyseCoverage(coverageDirPath) {
|
24 | if (typeof coverageDirPath !== 'string')
|
25 | throw new TypeError('First argument `coverageDirPath` must be a string.');
|
26 |
|
27 | const coverageDirFileNames = await fs.promises.readdir(coverageDirPath);
|
28 | const filteredProcessCoverages = [];
|
29 |
|
30 | for (const fileName of coverageDirFileNames)
|
31 | if (fileName.startsWith('coverage-'))
|
32 | filteredProcessCoverages.push(
|
33 | fs.promises
|
34 | .readFile(join(coverageDirPath, fileName), 'utf8')
|
35 | .then((coverageFileJson) => {
|
36 | const { result } = JSON.parse(coverageFileJson);
|
37 | return {
|
38 |
|
39 | result: result.filter(
|
40 | ({ url }) =>
|
41 |
|
42 | url.startsWith('file://') &&
|
43 |
|
44 | !url.includes('/node_modules/') &&
|
45 |
|
46 | !url.includes('/test/') &&
|
47 |
|
48 | !/\.test\.\w+$/u.test(url) &&
|
49 |
|
50 | !/\/test\.\w+$/u.test(url)
|
51 | ),
|
52 | };
|
53 | })
|
54 | );
|
55 |
|
56 | const mergedCoverage = v8Coverage.mergeProcessCovs(
|
57 | await Promise.all(filteredProcessCoverages)
|
58 | );
|
59 |
|
60 |
|
61 | const analysis = {
|
62 |
|
63 | filesCount: 0,
|
64 |
|
65 |
|
66 | covered: [],
|
67 |
|
68 |
|
69 | ignored: [],
|
70 |
|
71 |
|
72 | uncovered: [],
|
73 | };
|
74 |
|
75 | for (const { url, functions } of mergedCoverage.result) {
|
76 | analysis.filesCount++;
|
77 |
|
78 | const path = fileURLToPath(url);
|
79 | const uncoveredRanges = [];
|
80 |
|
81 | for (const { ranges } of functions)
|
82 | for (const range of ranges) if (!range.count) uncoveredRanges.push(range);
|
83 |
|
84 | if (uncoveredRanges.length) {
|
85 | const source = await fs.promises.readFile(path, 'utf8');
|
86 | const ignored = [];
|
87 | const uncovered = [];
|
88 |
|
89 | for (const range of uncoveredRanges) {
|
90 | const { ignore, ...rangeDetails } = sourceRange(
|
91 | source,
|
92 | range.startOffset,
|
93 |
|
94 |
|
95 |
|
96 | range.endOffset - 1
|
97 | );
|
98 |
|
99 | if (ignore) ignored.push(rangeDetails);
|
100 | else uncovered.push(rangeDetails);
|
101 | }
|
102 |
|
103 | if (ignored.length) analysis.ignored.push({ path, ranges: ignored });
|
104 | if (uncovered.length)
|
105 | analysis.uncovered.push({ path, ranges: uncovered });
|
106 | } else analysis.covered.push(path);
|
107 | }
|
108 |
|
109 | return analysis;
|
110 | }
|