@plugjs/cov8
Version:
V8 Coverage Plugin for the PlugJS Build System ==============================================
124 lines (123 loc) • 4.97 kB
JavaScript
// coverage.ts
import { sep } from "node:path";
import { html, initFunction } from "@plugjs/cov8-html";
import { assert } from "@plugjs/plug/asserts";
import { Files } from "@plugjs/plug/files";
import { $gry, $ms, $p, $plur, $red, $ylw, ERROR, NOTICE, WARN } from "@plugjs/plug/logging";
import { resolveAbsolutePath } from "@plugjs/plug/paths";
import { walk } from "@plugjs/plug/utils";
import { createAnalyser } from "./analysis.mjs";
import { coverageReport } from "./report.mjs";
var Coverage = class {
constructor(_coverageDir, _options = {}) {
this._coverageDir = _coverageDir;
this._options = _options;
}
async pipe(files, context) {
const coverageDir = context.resolve(this._coverageDir);
const coverageFiles = [];
for await (const file of walk(coverageDir, ["coverage-*.json"])) {
coverageFiles.push(resolveAbsolutePath(coverageDir, file));
}
assert(coverageFiles.length > 0, `No coverage files found in ${$p(coverageDir)}`);
const sourceFiles = [...files.absolutePaths()];
const ms1 = Date.now();
const analyser = await createAnalyser(
sourceFiles,
coverageFiles,
this._options.sourceMapBias || "least_upper_bound",
context.log
);
context.log.info("Parsed", coverageFiles.length, "coverage files", $ms(Date.now() - ms1));
const ms2 = Date.now();
const report = await coverageReport(analyser, sourceFiles, context.log);
context.log.info("Analysed", sourceFiles.length, "source files", $ms(Date.now() - ms2));
analyser.destroy();
const {
minimumCoverage = 50,
minimumFileCoverage = minimumCoverage,
optimalCoverage = Math.round((100 + minimumCoverage) / 2),
optimalFileCoverage = Math.round((100 + minimumFileCoverage) / 2)
} = this._options;
let max = 0;
for (const file in report) {
if (file.length > max) max = file.length;
}
let maxLength = 0;
for (const file in report.results) {
if (file.length > maxLength) maxLength = file.length;
}
let fileErrors = 0;
let fileWarnings = 0;
const _report = context.log.report("Coverage report");
for (const [_file, result] of Object.entries(report.results)) {
const { coverage } = result.nodeCoverage;
const file = _file;
if (coverage == null) {
_report.annotate(NOTICE, file, "n/a");
} else if (coverage < minimumFileCoverage) {
_report.annotate(ERROR, file, `${coverage} %`);
fileErrors++;
} else if (coverage < optimalFileCoverage) {
_report.annotate(WARN, file, `${coverage} %`);
fileWarnings++;
} else {
_report.annotate(NOTICE, file, `${coverage} %`);
}
}
if (report.nodes.coverage == null) {
const message = "No coverage data collected";
_report.add({ level: WARN, message });
} else if (report.nodes.coverage < minimumCoverage) {
const message = `${$red(`${report.nodes.coverage}%`)} does not meet minimum coverage ${$gry(`(${minimumCoverage}%)`)}`;
_report.add({ level: ERROR, message });
} else if (report.nodes.coverage < optimalCoverage) {
const message = `${$ylw(`${report.nodes.coverage}%`)} does not meet optimal coverage ${$gry(`(${optimalCoverage}%)`)}`;
_report.add({ level: WARN, message });
}
if (fileErrors) {
const f = $plur(fileErrors, "file does", "files do");
const message = `${f} not meet minimum file coverage ${$gry(`(${minimumFileCoverage}%)`)}`;
_report.add({ level: ERROR, message });
}
if (fileWarnings) {
const f = $plur(fileWarnings, "file does", "files do");
const message = `${f} not meet optimal file coverage ${$gry(`(${optimalFileCoverage}%)`)}`;
_report.add({ level: WARN, message });
}
if (this._options.reportDir == null) return _report.done(false);
const reportDir = context.resolve(this._options.reportDir);
const builder = Files.builder(reportDir);
const date = (/* @__PURE__ */ new Date()).toISOString();
const thresholds = {
minimumCoverage,
minimumFileCoverage,
optimalCoverage,
optimalFileCoverage
};
await builder.write("report.json", JSON.stringify({ ...report, thresholds, date }));
await builder.write("index.html", html);
const results = {};
for (const [rel, abs] of files.pathMappings()) {
results[rel] = report.results[abs];
}
const tree = {};
for (const relative of Object.keys(results)) {
const directories = relative.split(sep);
const file = directories.pop();
let node = tree;
for (const dir of directories) {
node = node[dir] = node[dir] || {};
}
node[file] = relative;
}
const jsonp = JSON.stringify({ ...report, results, thresholds, tree, date });
await builder.write("report.js", `${initFunction}(${jsonp});`);
_report.done(false);
return builder.build();
}
};
export {
Coverage
};
//# sourceMappingURL=coverage.mjs.map