UNPKG

vitest-github-action

Version:

GitHub actions error and coverage reporter for vitest.

1,164 lines (1,147 loc) 36.6 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // node_modules/istanbul-lib-coverage/lib/percent.js var require_percent = __commonJS({ "node_modules/istanbul-lib-coverage/lib/percent.js"(exports, module) { "use strict"; module.exports = function percent(covered, total) { let tmp; if (total > 0) { tmp = 1e3 * 100 * covered / total; return Math.floor(tmp / 10) / 100; } else { return 100; } }; } }); // node_modules/istanbul-lib-coverage/lib/data-properties.js var require_data_properties = __commonJS({ "node_modules/istanbul-lib-coverage/lib/data-properties.js"(exports, module) { "use strict"; module.exports = function dataProperties(klass, properties) { properties.forEach((p) => { Object.defineProperty(klass.prototype, p, { enumerable: true, get() { return this.data[p]; } }); }); }; } }); // node_modules/istanbul-lib-coverage/lib/coverage-summary.js var require_coverage_summary = __commonJS({ "node_modules/istanbul-lib-coverage/lib/coverage-summary.js"(exports, module) { "use strict"; var percent = require_percent(); var dataProperties = require_data_properties(); function blankSummary() { const empty = () => ({ total: 0, covered: 0, skipped: 0, pct: "Unknown" }); return { lines: empty(), statements: empty(), functions: empty(), branches: empty(), branchesTrue: empty() }; } function assertValidSummary(obj) { const valid = obj && obj.lines && obj.statements && obj.functions && obj.branches; if (!valid) { throw new Error( "Invalid summary coverage object, missing keys, found:" + Object.keys(obj).join(",") ); } } var CoverageSummary = class { /** * @constructor * @param {Object|CoverageSummary} [obj=undefined] an optional data object or * another coverage summary to initialize this object with. */ constructor(obj) { if (!obj) { this.data = blankSummary(); } else if (obj instanceof CoverageSummary) { this.data = obj.data; } else { this.data = obj; } assertValidSummary(this.data); } /** * merges a second summary coverage object into this one * @param {CoverageSummary} obj - another coverage summary object */ merge(obj) { const keys = [ "lines", "statements", "branches", "functions", "branchesTrue" ]; keys.forEach((key) => { if (obj[key]) { this[key].total += obj[key].total; this[key].covered += obj[key].covered; this[key].skipped += obj[key].skipped; this[key].pct = percent(this[key].covered, this[key].total); } }); return this; } /** * returns a POJO that is JSON serializable. May be used to get the raw * summary object. */ toJSON() { return this.data; } /** * return true if summary has no lines of code */ isEmpty() { return this.lines.total === 0; } }; dataProperties(CoverageSummary, [ "lines", "statements", "functions", "branches", "branchesTrue" ]); module.exports = { CoverageSummary }; } }); // node_modules/istanbul-lib-coverage/lib/file-coverage.js var require_file_coverage = __commonJS({ "node_modules/istanbul-lib-coverage/lib/file-coverage.js"(exports, module) { "use strict"; var percent = require_percent(); var dataProperties = require_data_properties(); var { CoverageSummary } = require_coverage_summary(); function emptyCoverage(filePath, reportLogic) { const cov = { path: filePath, statementMap: {}, fnMap: {}, branchMap: {}, s: {}, f: {}, b: {} }; if (reportLogic) cov.bT = {}; return cov; } function assertValidObject(obj) { const valid = obj && obj.path && obj.statementMap && obj.fnMap && obj.branchMap && obj.s && obj.f && obj.b; if (!valid) { throw new Error( "Invalid file coverage object, missing keys, found:" + Object.keys(obj).join(",") ); } } var keyFromLoc = ({ start, end }) => `${start.line}|${start.column}|${end.line}|${end.column}`; var mergeProp = (aHits, aMap, bHits, bMap, itemKey = keyFromLoc) => { const aItems = {}; for (const [key, itemHits] of Object.entries(aHits)) { const item = aMap[key]; aItems[itemKey(item)] = [itemHits, item]; } for (const [key, bItemHits] of Object.entries(bHits)) { const bItem = bMap[key]; const k = itemKey(bItem); if (aItems[k]) { const aPair = aItems[k]; if (bItemHits.forEach) { bItemHits.forEach((hits2, h) => { if (aPair[0][h] !== void 0) aPair[0][h] += hits2; else aPair[0][h] = hits2; }); } else { aPair[0] += bItemHits; } } else { aItems[k] = [bItemHits, bItem]; } } const hits = {}; const map = {}; Object.values(aItems).forEach(([itemHits, item], i) => { hits[i] = itemHits; map[i] = item; }); return [hits, map]; }; var FileCoverage = class { /** * @constructor * @param {Object|FileCoverage|String} pathOrObj is a string that initializes * and empty coverage object with the specified file path or a data object that * has all the required properties for a file coverage object. */ constructor(pathOrObj, reportLogic = false) { if (!pathOrObj) { throw new Error( "Coverage must be initialized with a path or an object" ); } if (typeof pathOrObj === "string") { this.data = emptyCoverage(pathOrObj, reportLogic); } else if (pathOrObj instanceof FileCoverage) { this.data = pathOrObj.data; } else if (typeof pathOrObj === "object") { this.data = pathOrObj; } else { throw new Error("Invalid argument to coverage constructor"); } assertValidObject(this.data); } /** * returns computed line coverage from statement coverage. * This is a map of hits keyed by line number in the source. */ getLineCoverage() { const statementMap = this.data.statementMap; const statements = this.data.s; const lineMap = /* @__PURE__ */ Object.create(null); Object.entries(statements).forEach(([st, count]) => { if (!statementMap[st]) { return; } const { line } = statementMap[st].start; const prevVal = lineMap[line]; if (prevVal === void 0 || prevVal < count) { lineMap[line] = count; } }); return lineMap; } /** * returns an array of uncovered line numbers. * @returns {Array} an array of line numbers for which no hits have been * collected. */ getUncoveredLines() { const lc = this.getLineCoverage(); const ret = []; Object.entries(lc).forEach(([l, hits]) => { if (hits === 0) { ret.push(l); } }); return ret; } /** * returns a map of branch coverage by source line number. * @returns {Object} an object keyed by line number. Each object * has a `covered`, `total` and `coverage` (percentage) property. */ getBranchCoverageByLine() { const branchMap = this.branchMap; const branches = this.b; const ret = {}; Object.entries(branchMap).forEach(([k, map]) => { const line = map.line || map.loc.start.line; const branchData = branches[k]; ret[line] = ret[line] || []; ret[line].push(...branchData); }); Object.entries(ret).forEach(([k, dataArray]) => { const covered = dataArray.filter((item) => item > 0); const coverage = covered.length / dataArray.length * 100; ret[k] = { covered: covered.length, total: dataArray.length, coverage }; }); return ret; } /** * return a JSON-serializable POJO for this file coverage object */ toJSON() { return this.data; } /** * merges a second coverage object into this one, updating hit counts * @param {FileCoverage} other - the coverage object to be merged into this one. * Note that the other object should have the same structure as this one (same file). */ merge(other) { if (other.all === true) { return; } if (this.all === true) { this.data = other.data; return; } let [hits, map] = mergeProp( this.s, this.statementMap, other.s, other.statementMap ); this.data.s = hits; this.data.statementMap = map; const keyFromLocProp = (x) => keyFromLoc(x.loc); const keyFromLocationsProp = (x) => keyFromLoc(x.locations[0]); [hits, map] = mergeProp( this.f, this.fnMap, other.f, other.fnMap, keyFromLocProp ); this.data.f = hits; this.data.fnMap = map; [hits, map] = mergeProp( this.b, this.branchMap, other.b, other.branchMap, keyFromLocationsProp ); this.data.b = hits; this.data.branchMap = map; if (this.bT && other.bT) { [hits, map] = mergeProp( this.bT, this.branchMap, other.bT, other.branchMap, keyFromLocationsProp ); this.data.bT = hits; } } computeSimpleTotals(property) { let stats = this[property]; if (typeof stats === "function") { stats = stats.call(this); } const ret = { total: Object.keys(stats).length, covered: Object.values(stats).filter((v) => !!v).length, skipped: 0 }; ret.pct = percent(ret.covered, ret.total); return ret; } computeBranchTotals(property) { const stats = this[property]; const ret = { total: 0, covered: 0, skipped: 0 }; Object.values(stats).forEach((branches) => { ret.covered += branches.filter((hits) => hits > 0).length; ret.total += branches.length; }); ret.pct = percent(ret.covered, ret.total); return ret; } /** * resets hit counts for all statements, functions and branches * in this coverage object resulting in zero coverage. */ resetHits() { const statements = this.s; const functions = this.f; const branches = this.b; const branchesTrue = this.bT; Object.keys(statements).forEach((s) => { statements[s] = 0; }); Object.keys(functions).forEach((f) => { functions[f] = 0; }); Object.keys(branches).forEach((b) => { branches[b].fill(0); }); if (branchesTrue) { Object.keys(branchesTrue).forEach((bT) => { branchesTrue[bT].fill(0); }); } } /** * returns a CoverageSummary for this file coverage object * @returns {CoverageSummary} */ toSummary() { const ret = {}; ret.lines = this.computeSimpleTotals("getLineCoverage"); ret.functions = this.computeSimpleTotals("f", "fnMap"); ret.statements = this.computeSimpleTotals("s", "statementMap"); ret.branches = this.computeBranchTotals("b"); if (this["bt"]) { ret.branchesTrue = this.computeBranchTotals("bT"); } return new CoverageSummary(ret); } }; dataProperties(FileCoverage, [ "path", "statementMap", "fnMap", "branchMap", "s", "f", "b", "bT", "all" ]); module.exports = { FileCoverage }; } }); // node_modules/istanbul-lib-coverage/lib/coverage-map.js var require_coverage_map = __commonJS({ "node_modules/istanbul-lib-coverage/lib/coverage-map.js"(exports, module) { "use strict"; var { FileCoverage } = require_file_coverage(); var { CoverageSummary } = require_coverage_summary(); function maybeConstruct(obj, klass) { if (obj instanceof klass) { return obj; } return new klass(obj); } function loadMap(source) { const data = /* @__PURE__ */ Object.create(null); if (!source) { return data; } Object.entries(source).forEach(([k, cov]) => { data[k] = maybeConstruct(cov, FileCoverage); }); return data; } var CoverageMap = class { /** * @constructor * @param {Object} [obj=undefined] obj A coverage map from which to initialize this * map's contents. This can be the raw global coverage object. */ constructor(obj) { if (obj instanceof CoverageMap) { this.data = obj.data; } else { this.data = loadMap(obj); } } /** * merges a second coverage map into this one * @param {CoverageMap} obj - a CoverageMap or its raw data. Coverage is merged * correctly for the same files and additional file coverage keys are created * as needed. */ merge(obj) { const other = maybeConstruct(obj, CoverageMap); Object.values(other.data).forEach((fc) => { this.addFileCoverage(fc); }); } /** * filter the coveragemap based on the callback provided * @param {Function (filename)} callback - Returns true if the path * should be included in the coveragemap. False if it should be * removed. */ filter(callback) { Object.keys(this.data).forEach((k) => { if (!callback(k)) { delete this.data[k]; } }); } /** * returns a JSON-serializable POJO for this coverage map * @returns {Object} */ toJSON() { return this.data; } /** * returns an array for file paths for which this map has coverage * @returns {Array{string}} - array of files */ files() { return Object.keys(this.data); } /** * returns the file coverage for the specified file. * @param {String} file * @returns {FileCoverage} */ fileCoverageFor(file) { const fc = this.data[file]; if (!fc) { throw new Error(`No file coverage available for: ${file}`); } return fc; } /** * adds a file coverage object to this map. If the path for the object, * already exists in the map, it is merged with the existing coverage * otherwise a new key is added to the map. * @param {FileCoverage} fc the file coverage to add */ addFileCoverage(fc) { const cov = new FileCoverage(fc); const { path: path2 } = cov; if (this.data[path2]) { this.data[path2].merge(cov); } else { this.data[path2] = cov; } } /** * returns the coverage summary for all the file coverage objects in this map. * @returns {CoverageSummary} */ getCoverageSummary() { const ret = new CoverageSummary(); Object.values(this.data).forEach((fc) => { ret.merge(fc.toSummary()); }); return ret; } }; module.exports = { CoverageMap }; } }); // node_modules/istanbul-lib-coverage/index.js var require_istanbul_lib_coverage = __commonJS({ "node_modules/istanbul-lib-coverage/index.js"(exports, module) { "use strict"; var { FileCoverage } = require_file_coverage(); var { CoverageMap } = require_coverage_map(); var { CoverageSummary } = require_coverage_summary(); module.exports = { /** * creates a coverage summary object * @param {Object} obj an argument with the same semantics * as the one passed to the `CoverageSummary` constructor * @returns {CoverageSummary} */ createCoverageSummary(obj) { if (obj && obj instanceof CoverageSummary) { return obj; } return new CoverageSummary(obj); }, /** * creates a CoverageMap object * @param {Object} obj optional - an argument with the same semantics * as the one passed to the CoverageMap constructor. * @returns {CoverageMap} */ createCoverageMap(obj) { if (obj && obj instanceof CoverageMap) { return obj; } return new CoverageMap(obj); }, /** * creates a FileCoverage object * @param {Object} obj optional - an argument with the same semantics * as the one passed to the FileCoverage constructor. * @returns {FileCoverage} */ createFileCoverage(obj) { if (obj && obj instanceof FileCoverage) { return obj; } return new FileCoverage(obj); } }; module.exports.classes = { /** * the file coverage constructor */ FileCoverage }; } }); // src/GithubReporter.ts import { endGroup, startGroup, error as actionsError } from "@actions/core"; // src/coverage/GithubIstanbulCoverageProviderModule.ts var import_istanbul_lib_coverage = __toESM(require_istanbul_lib_coverage(), 1); import libSourceMaps from "istanbul-lib-source-maps"; import libReport3 from "istanbul-lib-report"; import reports from "istanbul-reports"; // src/coverage/GithubIstanbulCoverageReporter.ts import libReport from "istanbul-lib-report"; var GithubIstanbulCoverageReporter = class extends libReport.ReportBase { octokit; github; constructor(options) { super(); this.octokit = options.octokit; this.github = options.github; } // // onStart(root: any, context: Context) { // this.contentWriter = context.writer.writeFile(this.file); // this.contentWriter.write('{'); // } // // onDetail(node: any) { // const fc = node.getFileCoverage(); // const key = fc.path; // const cw = this.contentWriter; // // if (this.first) { // this.first = false; // } else { // cw.write(','); // } // // cw.write(JSON.stringify(key)); // cw.write(': '); // cw.write(JSON.stringify(fc)); // cw.println(''); // } // // onEnd() { // const cw = this.contentWriter; // cw.println('}'); // cw.close(); // } }; var GithubIstanbulCoverageReporter_default = GithubIstanbulCoverageReporter; // src/coverage/GithubSummaryIstanbulCoverageReporter.ts import libReport2 from "istanbul-lib-report"; // src/coverage/helper.ts var statuses = { red: "\u{1F534}", green: "\u{1F7E2}", blue: "\u{1F535}" }; var getStatus = (attribute) => { let status = statuses.red; if (attribute.pct >= 50 && attribute.pct < 80) { status = statuses.blue; } else if (attribute.pct >= 80) { status = statuses.green; } return `<td align="center">${status}</td>`; }; var getAttributeRow = (attribute) => `<td align="center">${attribute.pct}% (${attribute.covered} / ${attribute.total})</td> `; // src/coverage/GithubSummaryIstanbulCoverageReporter.ts import { summary } from "@actions/core"; var htmlTableStart = ` <table> <thead> <tr> <th align="center">Status</th> <th align="center">Lines</th> <th align="center">Statements</th> <th align="center">Functions</th> <th align="center">Branches</th> </tr> </thead> <tbody> `; var htmlFilesTableStart = ` <table> <thead> <tr> <th align="center">File</th> <th align="center">Lines</th> <th align="center">Statements</th> <th align="center">Functions</th> <th align="center">Branches</th> </tr> </thead> <tbody> `; var htmlTableEnd = ` </tbody> </table> `; var GithubSummaryIstanbulCoverageReporter = class extends libReport2.ReportBase { octokit; github; report; filesReport; constructor(options) { super(); this.octokit = options.octokit; this.github = options.github; this.report = ""; this.filesReport = ""; } onStart() { this.report += htmlTableStart; this.filesReport += htmlFilesTableStart; } writeSummary(sc) { this.report += "<tr>\n"; this.report += getStatus(sc.lines); this.report += getAttributeRow(sc.lines); this.report += getAttributeRow(sc.statements); this.report += getAttributeRow(sc.functions); this.report += getAttributeRow(sc.branches); this.report += "</tr>\n"; } writeFileSummary(filePath, sc) { this.filesReport += "<tr>\n"; this.filesReport += `<td alight="center">${filePath}</td>`; this.filesReport += getAttributeRow(sc.lines); this.filesReport += getAttributeRow(sc.statements); this.filesReport += getAttributeRow(sc.functions); this.filesReport += getAttributeRow(sc.branches); this.filesReport += "</tr>\n"; } onSummary(node) { if (!node.isRoot()) { return; } this.writeSummary(node.getCoverageSummary(false)); } onDetail(node) { this.writeFileSummary( node.getFileCoverage().path, node.getCoverageSummary(false) ); } async onEnd() { this.report += htmlTableEnd; this.filesReport += htmlTableEnd; const reportSummary = summary.addHeading("Coverage Summary").addRaw(this.report).stringify(); await summary.clear(); const filesReportSummary = summary.addHeading("Coverage Summary for all Files").addDetails("Click to expand", this.filesReport).stringify(); const prNumber = this.github.context.payload.pull_request?.number; if (prNumber) { await this.octokit.rest.issues.createComment({ owner: this.github.context.repo.owner, repo: this.github.context.repo.repo, // eslint-disable-next-line @typescript-eslint/naming-convention issue_number: prNumber, body: reportSummary }); await this.octokit.rest.issues.createComment({ owner: this.github.context.repo.owner, repo: this.github.context.repo.repo, // eslint-disable-next-line @typescript-eslint/naming-convention issue_number: prNumber, body: filesReportSummary }); } } }; var GithubSummaryIstanbulCoverageReporter_default = GithubSummaryIstanbulCoverageReporter; // src/coverage/GithubIstanbulCoverageProviderModule.ts import { existsSync, promises as fs } from "node:fs"; // node_modules/pathe/dist/shared/pathe.92c04245.mjs function normalizeWindowsPath(input = "") { if (!input || !input.includes("\\")) { return input; } return input.replace(/\\/g, "/"); } var _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/; function cwd() { if (typeof process !== "undefined") { return process.cwd().replace(/\\/g, "/"); } return "/"; } var resolve = function(...arguments_) { arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument)); let resolvedPath = ""; let resolvedAbsolute = false; for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) { const path2 = index >= 0 ? arguments_[index] : cwd(); if (!path2 || path2.length === 0) { continue; } resolvedPath = `${path2}/${resolvedPath}`; resolvedAbsolute = isAbsolute(path2); } resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute); if (resolvedAbsolute && !isAbsolute(resolvedPath)) { return `/${resolvedPath}`; } return resolvedPath.length > 0 ? resolvedPath : "."; }; function normalizeString(path2, allowAboveRoot) { let res = ""; let lastSegmentLength = 0; let lastSlash = -1; let dots = 0; let char = null; for (let index = 0; index <= path2.length; ++index) { if (index < path2.length) { char = path2[index]; } else if (char === "/") { break; } else { char = "/"; } if (char === "/") { if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") { if (res.length > 2) { const lastSlashIndex = res.lastIndexOf("/"); if (lastSlashIndex === -1) { res = ""; lastSegmentLength = 0; } else { res = res.slice(0, lastSlashIndex); lastSegmentLength = res.length - 1 - res.lastIndexOf("/"); } lastSlash = index; dots = 0; continue; } else if (res.length > 0) { res = ""; lastSegmentLength = 0; lastSlash = index; dots = 0; continue; } } if (allowAboveRoot) { res += res.length > 0 ? "/.." : ".."; lastSegmentLength = 2; } } else { if (res.length > 0) { res += `/${path2.slice(lastSlash + 1, index)}`; } else { res = path2.slice(lastSlash + 1, index); } lastSegmentLength = index - lastSlash - 1; } lastSlash = index; dots = 0; } else if (char === "." && dots !== -1) { ++dots; } else { dots = -1; } } return res; } var isAbsolute = function(p) { return _IS_ABSOLUTE_RE.test(p); }; var relative = function(from, to) { const _from = resolve(from).split("/"); const _to = resolve(to).split("/"); const _fromCopy = [..._from]; for (const segment of _fromCopy) { if (_to[0] !== segment) { break; } _from.shift(); _to.shift(); } return [..._from.map(() => ".."), ..._to].join("/"); }; // src/coverage/GithubIstanbulCoverageProviderModule.ts import { coverageConfigDefaults, defaultExclude, defaultInclude } from "vitest/config"; import { BaseCoverageProvider } from "vitest/coverage"; import { createInstrumenter } from "istanbul-lib-instrument"; import _TestExclude from "test-exclude"; import github from "@actions/github"; import { error } from "@actions/core"; var githubIstanbulCoverageProviderModule = { getProvider() { return new GithubIstanbulCoverageProvider(); } // Implements rest of the CoverageProviderModule ... }; var coverageStorageKey = "__VITEST_COVERAGE__"; var { GITHUB_TOKEN: githubTokenEnv = "", GH_TOKEN: ghTokenEnv = "" } = process.env ?? {}; var githubToken = githubTokenEnv ?? ghTokenEnv; var GithubIstanbulCoverageProvider = class extends BaseCoverageProvider { name = "github-istanbul"; ctx; options; instrumenter; testExclude; initialize(ctx) { const config = ctx.config.coverage; this.ctx = ctx; this.options = { ...coverageConfigDefaults, // User's options ...config, // Resolved fields provider: "istanbul", reportsDirectory: resolve( ctx.config.root, config.reportsDirectory ?? coverageConfigDefaults.reportsDirectory ), reporter: this.resolveReporters( config.reporter ?? coverageConfigDefaults.reporter ) }; this.instrumenter = createInstrumenter({ produceSourceMap: true, autoWrap: false, esModules: true, compact: false, coverageVariable: coverageStorageKey, // @ts-expect-error missing type coverageGlobalScope: "globalThis", coverageGlobalScopeFunc: false, ignoreClassMethods: this.options.ignoreClassMethods }); this.testExclude = new _TestExclude({ cwd: ctx.config.root, include: typeof this.options.include === "undefined" ? void 0 : [...this.options.include], exclude: [...defaultExclude, ...defaultInclude, ...this.options.exclude], excludeNodeModules: true, extension: this.options.extension }); } resolveOptions() { return this.options; } onFileTransform(sourceCode, id, pluginCtx) { if (!this.testExclude.shouldInstrument(id)) { return; } const sourceMap = pluginCtx.getCombinedSourcemap(); if (!sourceMap) { return; } sourceMap.sources = sourceMap.sources.map(removeQueryParameters); const code = this.instrumenter.instrumentSync(sourceCode, id, sourceMap); const map = this.instrumenter.lastSourceMap(); return { code, map }; } onAfterSuiteRun({ coverage }) { this.coverages.push(coverage); } async clean(clean = true) { if (clean && existsSync(this.options.reportsDirectory)) { await fs.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 }); } this.coverages = []; } async reportCoverage({ allTestsRun } = {}) { const mergedCoverage = this.coverages.reduce( (coverage, previousCoverageMap) => { const map = import_istanbul_lib_coverage.default.createCoverageMap(coverage); map.merge(previousCoverageMap); return map; }, import_istanbul_lib_coverage.default.createCoverageMap({}) ); if (this.options.all && allTestsRun) { await this.includeUntestedFiles(mergedCoverage); } includeImplicitElseBranches(mergedCoverage); const sourceMapStore = libSourceMaps.createSourceMapStore(); const coverageMap = await sourceMapStore.transformCoverage( mergedCoverage ); const context = libReport3.createContext({ dir: this.options.reportsDirectory, coverageMap, sourceFinder: sourceMapStore.sourceFinder, watermarks: this.options.watermarks }); for (const reporter of this.options.reporter) { if (["github", "github-summary"].includes(reporter[0])) { if (!githubToken) { error(`[${this.name}] Could not report coverage to PR as GITHUB_TOKEN or GH_TOKEN environment variable was not found. Skipping to the next reporter.`); continue; } const octokit = github.getOctokit(githubToken); if (reporter[0] === "github") { new GithubIstanbulCoverageReporter_default({ github, octokit, ...reporter[1] }).execute(context); continue; } if (reporter[0] === "github-summary") { new GithubSummaryIstanbulCoverageReporter_default({ github, octokit, ...reporter[1] }).execute(context); continue; } } reports.create(reporter[0], { skipFull: this.options.skipFull, projectRoot: this.ctx.config.root, ...reporter[1] }).execute(context); } if (this.options.branches ?? this.options.functions ?? this.options.lines ?? this.options.statements) { this.checkThresholds(coverageMap, { branches: this.options.branches, functions: this.options.functions, lines: this.options.lines, statements: this.options.statements }); } if (this.options.thresholdAutoUpdate && allTestsRun) { this.updateThresholds({ coverageMap, thresholds: { branches: this.options.branches, functions: this.options.functions, lines: this.options.lines, statements: this.options.statements }, perFile: this.options.perFile, configurationFile: this.ctx.server.config.configFile }); } } checkThresholds(coverageMap, thresholds) { const summaries = this.options.perFile ? coverageMap.files().map((file) => ({ file, summary: coverageMap.fileCoverageFor(file).toSummary() })) : [ { file: null, summary: coverageMap.getCoverageSummary() } ]; for (const { summary: summary2, file } of summaries) { for (const thresholdKey of [ "lines", "functions", "statements", "branches" ]) { const threshold = thresholds[thresholdKey]; if (!threshold) { continue; } const coverage = summary2.data[thresholdKey].pct; if (coverage < threshold) { process.exitCode = 1; let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet`; if (!this.options.perFile) { errorMessage += " global"; } errorMessage += ` threshold (${threshold}%)`; if (this.options.perFile && file) { errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`; } console.error(errorMessage); } } } } async includeUntestedFiles(coverageMap) { const includedFiles = await this.testExclude.glob(this.ctx.config.root); const uncoveredFiles = includedFiles.map((file) => resolve(this.ctx.config.root, file)).filter((file) => !coverageMap.data[file]); const transformResults = await Promise.all( uncoveredFiles.map(async (filename) => { const transformResult = await this.ctx.vitenode.transformRequest( filename ); return { transformResult, filename }; }) ); for (const { transformResult, filename } of transformResults) { const sourceMap = transformResult?.map; const code = transformResult?.code; if (sourceMap && code) { this.instrumenter.instrumentSync(code, filename, sourceMap); const lastCoverage = this.instrumenter.lastFileCoverage(); if (lastCoverage) { coverageMap.addFileCoverage(lastCoverage); } } } } }; function removeQueryParameters(filename) { return filename.split("?")[0]; } function isEmptyCoverageRange(range) { return range.start === void 0 || range.start.line === void 0 || range.start.column === void 0 || range.end === void 0 || range.end.line === void 0 || range.end.column === void 0; } function includeImplicitElseBranches(coverageMap) { for (const file of coverageMap.files()) { const fileCoverage = coverageMap.fileCoverageFor(file); for (const branchMap of Object.values(fileCoverage.branchMap)) { if (branchMap.type !== "if") { continue; } const lastIndex = branchMap.locations.length - 1; if (lastIndex > 0) { const elseLocation = branchMap.locations[lastIndex]; if (elseLocation && isEmptyCoverageRange(elseLocation)) { const ifLocation = branchMap.locations[0]; elseLocation.start = { ...ifLocation.start }; elseLocation.end = { ...ifLocation.end }; } } } } } var GithubIstanbulCoverageProviderModule_default = githubIstanbulCoverageProviderModule; // src/github-istanbul-coverage-provider.ts var github_istanbul_coverage_provider_default = GithubIstanbulCoverageProviderModule_default; export { github_istanbul_coverage_provider_default as default };