-
Notifications
You must be signed in to change notification settings - Fork 20
/
generateFileCoverageHtml.ts
143 lines (126 loc) · 3.86 KB
/
generateFileCoverageHtml.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import * as path from "node:path";
import { oneLine } from "common-tags";
import { FileCoverageMode } from "../inputs/FileCoverageMode";
import type { JsonFinal } from "../types/JsonFinal";
import type { JsonSummary } from "../types/JsonSummary";
import { generateBlobFileUrl } from "./generateFileUrl";
import {
type LineRange,
getUncoveredLinesFromStatements,
} from "./getUncoveredLinesFromStatements";
type FileCoverageInputs = {
jsonSummary: JsonSummary;
jsonFinal: JsonFinal;
fileCoverageMode: FileCoverageMode;
pullChanges: string[];
commitSHA: string;
};
const workspacePath = process.cwd();
const generateFileCoverageHtml = ({
jsonSummary,
jsonFinal,
fileCoverageMode,
pullChanges,
commitSHA,
}: FileCoverageInputs) => {
const filePaths = Object.keys(jsonSummary).filter((key) => key !== "total");
const formatFileLine = (filePath: string) => {
const coverageSummary = jsonSummary[filePath];
const lineCoverage = jsonFinal[filePath];
// LineCoverage might be empty if coverage-final.json was not provided.
const uncoveredLines = lineCoverage
? getUncoveredLinesFromStatements(jsonFinal[filePath])
: [];
const relativeFilePath = path.relative(workspacePath, filePath);
const url = generateBlobFileUrl(relativeFilePath, commitSHA);
return `
<tr>
<td align="left"><a href="${url}">${relativeFilePath}</a></td>
<td align="right">${coverageSummary.statements.pct}%</td>
<td align="right">${coverageSummary.branches.pct}%</td>
<td align="right">${coverageSummary.functions.pct}%</td>
<td align="right">${coverageSummary.lines.pct}%</td>
<td align="left">${createRangeURLs(uncoveredLines, url)}</td>
</tr>`;
};
let reportData = "";
const [changedFiles, unchangedFiles] = splitFilesByChangeStatus(
filePaths,
pullChanges,
);
if (
fileCoverageMode === FileCoverageMode.Changes &&
changedFiles.length === 0
) {
return "No changed files found.";
}
if (changedFiles.length > 0) {
reportData += `
${formatGroupLine("Changed Files")}
${changedFiles.map(formatFileLine).join("")}
`;
}
if (fileCoverageMode === FileCoverageMode.All && unchangedFiles.length > 0) {
reportData += `
${formatGroupLine("Unchanged Files")}
${unchangedFiles.map(formatFileLine).join("")}
`;
}
return oneLine`
<table>
<thead>
<tr>
<th align="left">File</th>
<th align="right">Stmts</th>
<th align="right">% Branch</th>
<th align="right">% Funcs</th>
<th align="right">% Lines</th>
<th align="left">Uncovered Lines</th>
</tr>
</thead>
<tbody>
${reportData}
</tbody>
</table>
`;
};
function formatGroupLine(caption: string): string {
return `
<tr>
<td align="left" colspan="6"><b>${caption}</b></td>
</tr>
`;
}
function createRangeURLs(uncoveredLines: LineRange[], url: string): string {
return uncoveredLines
.map((range) => {
let linkText = `${range.start}`;
let urlHash = `#L${range.start}`;
if (range.start !== range.end) {
linkText += `-${range.end}`;
urlHash += `-L${range.end}`;
}
return `<a href="${url}${urlHash}" class="text-red">${linkText}</a>`;
})
.join(", ");
}
function splitFilesByChangeStatus(
filePaths: string[],
pullChanges: string[],
): [string[], string[]] {
return filePaths.reduce(
([changedFiles, unchangedFiles], filePath) => {
// Pull Changes has filePaths relative to the git repository, whereas the jsonSummary has filePaths relative to the workspace.
// So we have to convert the filePaths to be relative to the workspace.
const comparePath = path.relative(workspacePath, filePath);
if (pullChanges.includes(comparePath)) {
changedFiles.push(filePath);
} else {
unchangedFiles.push(filePath);
}
return [changedFiles, unchangedFiles];
},
[[], []] as [string[], string[]],
);
}
export { generateFileCoverageHtml };