Skip to content

Commit 9658581

Browse files
committed
refactor: parse-lcov
1 parent 5cecbfb commit 9658581

File tree

1 file changed

+104
-124
lines changed

1 file changed

+104
-124
lines changed

src/parse-lcov.ts

Lines changed: 104 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,121 @@
11
import { existsSync, readFile } from "fs";
22
import { Coverage, CoverageCollection } from "./coverage-info";
33

4-
function walkFile(str: string): Promise<CoverageCollection> {
5-
return new Promise((resolve, reject) => {
6-
let data: CoverageCollection = [];
7-
let item: Coverage | undefined;
8-
const lines = ["end_of_record"].concat(str.split("\n"));
4+
type MetricsMap = {
5+
[key: string]: (item: Coverage, args: string) => void;
6+
};
7+
8+
/**
9+
* @description This method creates a new Coverage object with default values.
10+
*/
11+
function createCoverageItem(): Coverage {
12+
return {
13+
file: "",
14+
title: "",
15+
lines: {
16+
found: 0,
17+
hit: 0,
18+
details: [],
19+
},
20+
functions: {
21+
hit: 0,
22+
found: 0,
23+
details: [],
24+
},
25+
branches: {
26+
hit: 0,
27+
found: 0,
28+
details: [],
29+
},
30+
};
31+
}
932

10-
for (let line of lines) {
11-
line = line.trim();
12-
const allparts = line.split(":");
13-
const metrics = allparts.shift();
14-
const args = allparts.join(":");
15-
const parts = [allparts.shift(), allparts.join(":")];
33+
/**
34+
* @description This object maps lcov metrics to a handler function.
35+
*/
36+
const metricsMap: MetricsMap = {
37+
TN: (item: Coverage, args: string) => (item.title = args.trim()),
38+
SF: (item: Coverage, args: string) => (item.file = args.trim()),
39+
LF: (item: Coverage, args: string) =>
40+
(item.lines.found = Number(args.trim())),
41+
LH: (item: Coverage, args: string) => (item.lines.hit = Number(args.trim())),
42+
DA: (item: Coverage, args: string) => {
43+
const details = args.split(",");
44+
item.lines.details.push({
45+
line: Number(details[0]),
46+
hit: Number(details[1]),
47+
});
48+
},
49+
FNF: (item: Coverage, args: string) =>
50+
(item.functions.found = Number(args.trim())),
51+
FNH: (item: Coverage, args: string) =>
52+
(item.functions.hit = Number(args.trim())),
53+
FNDA: (item: Coverage, args: string) => {
54+
const details = args.split(",");
55+
item.functions.details.push({
56+
line: Number(details[0]),
57+
name: details[1],
58+
});
59+
},
60+
BRF: (item: Coverage, args: string) =>
61+
(item.branches.found = Number(args.trim())),
62+
BRH: (item: Coverage, args: string) =>
63+
(item.branches.hit = Number(args.trim())),
64+
BRDA: (item: Coverage, args: string) => {
65+
const details = args.split(",");
66+
item.branches.details.push({
67+
line: Number(details[0]),
68+
block: Number(details[1]),
69+
branch: Number(details[2]),
70+
hit: details[3] === "-" ? 0 : Number(details[3]),
71+
});
72+
},
73+
};
1674

17-
if (item) {
18-
switch (metrics && metrics.toUpperCase()) {
19-
case "TN": {
20-
item.title = args.trim();
21-
break;
22-
}
23-
case "SF": {
24-
item.file = args.trim();
25-
break;
26-
}
27-
case "FNF": {
28-
item.functions.found = Number(args.trim());
29-
break;
30-
}
31-
case "FNH": {
32-
item.functions.hit = Number(args.trim());
33-
break;
34-
}
35-
case "LF": {
36-
item.lines.found = Number(args.trim());
37-
break;
38-
}
39-
case "LH": {
40-
item.lines.hit = Number(args.trim());
41-
break;
42-
}
43-
case "DA": {
44-
if (item) {
45-
const details = args.split(",");
46-
item.lines.details.push({
47-
line: Number(details[0]),
48-
hit: Number(details[1]),
49-
});
50-
}
51-
break;
52-
}
53-
case "BRF": {
54-
item.branches.found = Number(parts[1]);
55-
break;
56-
}
57-
case "BRH": {
58-
item.branches.hit = Number(parts[1]);
59-
break;
60-
}
61-
case "FN": {
62-
if (item) {
63-
const fn = args.split(",");
64-
item.functions.details.push({
65-
name: fn[1],
66-
line: Number(fn[0]),
67-
});
68-
}
69-
break;
70-
}
71-
case "FNDA": {
72-
if (item) {
73-
const fn = args.split(",");
74-
item.functions.details.some((i, k) => {
75-
if (item && item.functions.details) {
76-
if (i.name === fn[1] && i.hit === undefined) {
77-
item.functions.details[k].hit = Number(fn[0]);
78-
return true;
79-
}
80-
}
81-
return false;
82-
});
83-
}
84-
break;
85-
}
86-
case "BRDA": {
87-
if (item) {
88-
const fn = args.split(",");
89-
item.branches.details.push({
90-
line: Number(fn[0]),
91-
block: Number(fn[1]),
92-
branch: Number(fn[2]),
93-
hit: fn[3] === "-" ? 0 : Number(fn[3]),
94-
});
95-
}
96-
break;
97-
}
98-
}
99-
}
75+
/**
76+
* @description Parses a string of lcov data into a CoverageCollection
77+
* @param str The string to parse
78+
* @returns A CoverageCollection
79+
*/
80+
function parseFile(str: string): CoverageCollection {
81+
let data: CoverageCollection = [];
82+
let item: Coverage = createCoverageItem();
83+
const lines = str.split("\n");
10084

101-
if (line.indexOf("end_of_record") > -1) {
102-
item && data.push(item);
103-
item = {
104-
file: "",
105-
title: "",
106-
lines: {
107-
found: 0,
108-
hit: 0,
109-
details: [],
110-
},
111-
functions: {
112-
hit: 0,
113-
found: 0,
114-
details: [],
115-
},
116-
branches: {
117-
hit: 0,
118-
found: 0,
119-
details: [],
120-
},
121-
};
85+
for (let line of lines) {
86+
line = line.trim();
87+
const allparts = line.split(":");
88+
const metrics = allparts.shift();
89+
const args = allparts.join(":");
90+
91+
if (item && metrics) {
92+
const handler = metricsMap[metrics.toUpperCase()];
93+
if (handler) {
94+
handler(item, args);
12295
}
12396
}
12497

125-
if (data.length) {
126-
resolve(data);
127-
} else {
128-
reject("Failed to parse string");
98+
if (line.indexOf("end_of_record") > -1) {
99+
item && data.push(item);
100+
item = createCoverageItem();
129101
}
130-
});
102+
}
103+
104+
return data;
131105
}
132106

133-
export function parse(file: string): Promise<CoverageCollection> {
107+
/**
108+
* @description Parses a file of lcov data into a CoverageCollection
109+
* @param file The file to parse
110+
* @returns A CoverageCollection
111+
* @throws Error if the file does not exist
112+
*/
113+
export async function parse(file: string): Promise<CoverageCollection> {
134114
return new Promise((resolve, reject) => {
135115
!existsSync(file)
136-
? walkFile(file).then(resolve).catch(reject)
116+
? reject(new Error(`File not found: ${file}`))
137117
: readFile(file, "utf8", (_, str) => {
138-
walkFile(str).then(resolve).catch(reject);
139-
});
118+
resolve(parseFile(str));
119+
});
140120
});
141121
}

0 commit comments

Comments
 (0)