From d7c6dd97921d11c185f6b008b28f9d6203f7d996 Mon Sep 17 00:00:00 2001 From: cenfun Date: Sat, 27 Jul 2024 14:57:57 +0800 Subject: [PATCH] refactor reports --- lib/generate.js | 555 +++++--------------------------- lib/index.d.ts | 6 + lib/reports/codacy.js | 43 +++ lib/reports/codecov.js | 28 ++ lib/reports/console-details.js | 125 +++++++ lib/reports/console-summary.js | 83 +++++ lib/reports/markdown-details.js | 118 +++++++ lib/reports/markdown-summary.js | 45 +++ lib/reports/raw.js | 33 ++ test/test-pr.js | 22 +- 10 files changed, 567 insertions(+), 491 deletions(-) create mode 100644 lib/reports/codacy.js create mode 100644 lib/reports/codecov.js create mode 100644 lib/reports/console-details.js create mode 100644 lib/reports/console-summary.js create mode 100644 lib/reports/markdown-details.js create mode 100644 lib/reports/markdown-summary.js create mode 100644 lib/reports/raw.js diff --git a/lib/generate.js b/lib/generate.js index f18069f8..3d9a34f5 100644 --- a/lib/generate.js +++ b/lib/generate.js @@ -1,38 +1,78 @@ const fs = require('fs'); const path = require('path'); -const CG = require('console-grid'); -const EC = require('eight-colors'); const { pathToFileURL, fileURLToPath } = require('url'); +const EC = require('eight-colors'); +const { minimatch } = require('minimatch'); + const Util = require('./utils/util.js'); +const { convertV8List } = require('./converter/converter.js'); +const { resolveSourceMap } = require('./converter/collect-source-maps.js'); + +// ======================================================================================================== +// built-in reports + +// istanbul const { initIstanbulData, mergeIstanbulCoverage, saveIstanbulReports } = require('./istanbul/istanbul.js'); +// v8 const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js'); -const { convertV8List } = require('./converter/converter.js'); -const { resolveSourceMap } = require('./converter/collect-source-maps.js'); +// both +const { codecovReport } = require('./reports/codecov.js'); +const { codacyReport } = require('./reports/codacy.js'); -const { minimatch } = require('minimatch'); -const { getGroupedRows } = require('./utils/snapshot.js'); -const generateMarkdownGrid = require('./utils/markdown.js'); +const { consoleDetailsReport } = require('./reports/console-details.js'); +const { consoleSummaryReport } = require('./reports/console-summary.js'); -// ======================================================================================================== +const { markdownDetailsReport } = require('./reports/markdown-details.js'); +const { markdownSummaryReport } = require('./reports/markdown-summary.js'); -// maybe v8 or to istanbul reports -const generateV8ListReports = async (v8list, coverageData, fileSources, options) => { - let istanbulReportPath; - // v8 to istanbul reports - if (options.reportGroup.istanbul) { - const istanbulCoverageResults = await saveIstanbulReports(coverageData, fileSources, options); - istanbulReportPath = istanbulCoverageResults.reportPath; - } +const { rawReport } = require('./reports/raw.js'); - // v8 reports and v8 coverage results - // could be no v8 or v8-json, but requires v8 coverage results - const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath); - return v8CoverageResults; +const allBuiltInReports = { + // v8 + 'v8': 'v8', + 'v8-json': 'v8', + + // istanbul + 'clover': 'istanbul', + 'cobertura': 'istanbul', + 'html': 'istanbul', + 'html-spa': 'istanbul', + 'json': 'istanbul', + 'json-summary': 'istanbul', + 'lcov': 'istanbul', + 'lcovonly': 'istanbul', + 'none': 'istanbul', + 'teamcity': 'istanbul', + 'text': 'istanbul', + 'text-lcov': 'istanbul', + 'text-summary': 'istanbul', + + // both + 'codecov': 'both', + 'codacy': 'both', + 'console-details': 'both', + 'console-summary': 'both', + 'markdown-details': 'both', + 'markdown-summary': 'both', + 'raw': 'both' +}; + +const bothBuiltInReports = { + 'codecov': codecovReport, + 'codacy': codacyReport, + + 'console-details': consoleDetailsReport, + 'console-summary': consoleSummaryReport, + + 'markdown-details': markdownDetailsReport, + 'markdown-summary': markdownSummaryReport, + + 'raw': rawReport }; // ======================================================================================================== @@ -69,42 +109,12 @@ const getReportGroup = (reports, lcov, dataType) => { reportMap.lcovonly = {}; } - const allBuildInReports = { - // v8 - 'v8': 'v8', - 'v8-json': 'v8', - - // istanbul - 'clover': 'istanbul', - 'cobertura': 'istanbul', - 'html': 'istanbul', - 'html-spa': 'istanbul', - 'json': 'istanbul', - 'json-summary': 'istanbul', - 'lcov': 'istanbul', - 'lcovonly': 'istanbul', - 'none': 'istanbul', - 'teamcity': 'istanbul', - 'text': 'istanbul', - 'text-lcov': 'istanbul', - 'text-summary': 'istanbul', - - // both - 'codecov': 'both', - 'codacy': 'both', - 'console-details': 'both', - 'console-summary': 'both', - 'markdown-details': 'both', - 'markdown-summary': 'both', - 'raw': 'both' - }; - // group v8 and istanbul const reportGroup = {}; Object.keys(reportMap).forEach((k) => { const options = reportMap[k]; - let type = allBuildInReports[k]; + let type = allBuiltInReports[k]; if (!type) { // for custom reporter type = options.type || 'v8'; @@ -132,426 +142,21 @@ const getReportGroup = (reports, lcov, dataType) => { // ======================================================================================================== -const nFormatter = (v) => { - if (typeof v === 'number') { - return Util.NF(v); - } - return v; -}; - -const getRowData = (rowName, summary, metrics, color) => { - const summaryRow = {}; - let lowest = { - pct: 100, - status: 'high' - }; - metrics.map((k) => { - const s = summary[k]; - if (!s) { - return; - } - const percent = s.pct; - if (typeof percent !== 'number') { - return; - } - summaryRow[k] = Util.getColorStrByStatus(Util.PSF(percent, 100, 2), s.status, color); - if (percent < lowest.pct) { - lowest = s; - } - }); - summaryRow.nameStatus = lowest.status; - summaryRow.name = rowName; - return summaryRow; -}; - -const getDetailsRows = (files, metrics, cdOptions, color) => { - - const skipPercent = cdOptions.skipPercent; - if (typeof skipPercent === 'number' && skipPercent > 0) { - files = files.filter((file) => { - const { summary } = file; - for (const k of metrics) { - const percent = summary[k].pct; - if (typeof percent === 'number' && percent < skipPercent) { - return true; - } - } - return false; - }); - } - - const flatRows = []; - files.forEach((file) => { - - // do NOT add debug file - if (file.debug) { - return; - } - - const { sourcePath, summary } = file; - const fileRow = getRowData(sourcePath, summary, metrics, color); - fileRow.uncoveredLines = Util.getUncoveredLines(file.data.lines, color); - flatRows.push(fileRow); - }); - - return getGroupedRows(flatRows); -}; - -const handleCodecovReport = async (reportData, reportOptions, options) => { - const codecovOptions = { - outputFile: 'codecov.json', - ... reportOptions - }; - - const jsonPath = path.resolve(options.outputDir, codecovOptions.outputFile); - - // https://docs.codecov.com/docs/codecov-custom-coverage-format - const coverage = {}; - reportData.files.forEach((item) => { - const { sourcePath, data } = item; - coverage[sourcePath] = data.lines; - }); - const codecovData = { - coverage - }; - - await Util.writeFile(jsonPath, JSON.stringify(codecovData)); - return Util.relativePath(jsonPath); -}; - -const handleCodacyReport = async (reportData, reportOptions, options) => { - const codacyOptions = { - outputFile: 'codacy.json', - ... reportOptions - }; - - const jsonPath = path.resolve(options.outputDir, codacyOptions.outputFile); - - // https://api.codacy.com/swagger#tocscoveragereport - const fileReports = []; - reportData.files.forEach((item) => { - const { sourcePath, data } = item; - - const coverage = {}; - for (const [key, value] of Object.entries(data.lines)) { - if (typeof value === 'number') { - coverage[key] = value; - } else { - // partial coverage not supported? - coverage[key] = 0; - } - } - - fileReports.push({ - filename: sourcePath, - coverage - }); - }); - const codacyData = { - fileReports - }; - - await Util.writeFile(jsonPath, JSON.stringify(codacyData)); - return Util.relativePath(jsonPath); -}; - -const handleConsoleDetailsReport = (reportData, reportOptions, options) => { - const cdOptions = { - maxCols: 50, - skipPercent: 0, - metrics: [], - ... reportOptions - }; - - const { - type, name, summary, files - } = reportData; - - if (name) { - Util.logInfo(EC.cyan(name)); - } - - const color = 'ansicode'; - - const metrics = Util.getMetrics(cdOptions.metrics, type); - const rows = getDetailsRows(files, metrics, cdOptions, color); - const summaryRow = getRowData('Summary', summary, metrics, color); - if (summaryRow.nameStatus) { - summaryRow.name = Util.getColorStrByStatus(summaryRow.name, summaryRow.nameStatus, color); - } - // no rows if skipped all by skipPercent - if (rows.length) { - rows.push({ - innerBorder: true - }); - } - rows.push(summaryRow); - - const columns = [{ - id: 'name', - name: 'Name' - }, ... metrics.map((m) => { - return { - id: m, - name: Util.capitalizeFirstLetter(m), - align: 'right' - }; - }), { - id: 'uncoveredLines', - name: 'Uncovered Lines' - }]; - - return CG({ - options: { - silent: cdOptions.silent, - nullPlaceholder: '', - defaultMaxWidth: cdOptions.maxCols - }, - columns, - rows - }); -}; - -const getSummaryColumns = (color) => { - - const columns = [{ - id: 'name', - name: 'Name' - }, { - id: 'pct', - name: 'Coverage %', - align: 'right', - formatter: (v, row, column) => { - if (typeof v === 'number') { - return Util.getColorStrByStatus(Util.PSF(v, 100, 2), row.status, color); - } - return v; - } - }, { - id: 'covered', - name: 'Covered', - align: 'right', - formatter: nFormatter - }, { - id: 'uncovered', - name: 'Uncovered', - align: 'right', - formatter: nFormatter - }, { - id: 'total', - name: 'Total', - align: 'right', - formatter: nFormatter - }]; - - return columns; -}; - -const handleConsoleSummaryReport = (reportData, reportOptions, options) => { - - const csOptions = { - metrics: [], - ... reportOptions - }; - - const { - summary, name, type - } = reportData; - - if (name) { - Util.logInfo(EC.cyan(name)); - } - - const metrics = Util.getMetrics(csOptions.metrics, type); - - const rows = metrics.map((k) => { - return { - ... summary[k], - name: Util.capitalizeFirstLetter(k) - }; - }); - - const columns = getSummaryColumns('ansicode'); - - CG({ - columns, - rows - }); -}; - -const cutWithMaxCols = (str, maxCols, before) => { - if (str && maxCols && str.length > maxCols) { - if (before) { - return `... ${str.slice(str.length - (maxCols - 4))}`; - } - return `${str.slice(0, maxCols - 4)} ...`; - } - - return str; -}; - -const handleMarkdownDetailsReport = async (reportData, reportOptions, options) => { - const mdOptions = { - baseUrl: '', - // unicode, html, or empty for no color - color: 'unicode', - maxCols: 50, - skipPercent: 0, - metrics: [], - outputFile: 'coverage-details.md', - ... reportOptions - }; - - const baseUrl = mdOptions.baseUrl; - const color = Util.normalizeColorType(mdOptions.color); - const maxCols = Util.normalizeMaxCols(mdOptions.maxCols, 15); - - const mdPath = path.resolve(options.outputDir, mdOptions.outputFile); - - const metrics = Util.getMetrics(mdOptions.metrics, reportData.type); - - let files = reportData.files; - const skipPercent = mdOptions.skipPercent; - if (typeof skipPercent === 'number' && skipPercent > 0) { - files = files.filter((file) => { - const { summary } = file; - for (const k of metrics) { - const percent = summary[k].pct; - if (typeof percent === 'number' && percent < skipPercent) { - return true; - } - } - return false; - }); - } - - const rows = []; - files.forEach((file) => { - - // do NOT add debug file - if (file.debug) { - return; - } - - const { sourcePath, summary } = file; - - const fileRow = getRowData(sourcePath, summary, metrics, color); - - let rowName = cutWithMaxCols(fileRow.name, maxCols, true); - - if (fileRow.nameStatus) { - const nameColor = baseUrl && color === 'tex' ? '' : color; - rowName = Util.getColorStrByStatus(rowName, fileRow.nameStatus, nameColor); - } - if (baseUrl) { - rowName = `[${rowName}](${baseUrl + sourcePath})`; - } - - fileRow.name = rowName; - - const uncoveredLines = Util.getUncoveredLines(file.data.lines, color); - fileRow.uncoveredLines = cutWithMaxCols(uncoveredLines, maxCols); - - rows.push(fileRow); - }); - - const summaryRow = getRowData('Summary', reportData.summary, metrics, color); - if (summaryRow.nameStatus) { - summaryRow.name = Util.getColorStrByStatus(summaryRow.name, summaryRow.nameStatus, color); - } - rows.push(summaryRow); - - const columns = [{ - id: 'name', - name: 'Name' - }, ... metrics.map((m) => { - return { - id: m, - name: Util.capitalizeFirstLetter(m), - align: 'right' - }; - }), { - id: 'uncoveredLines', - name: 'Uncovered Lines' - }]; - - const markdownGrid = generateMarkdownGrid({ - options: { - name: reportData.name - }, - columns, - rows - }); - - await Util.writeFile(mdPath, markdownGrid); - return Util.relativePath(mdPath); -}; - -const handleMarkdownSummaryReport = async (reportData, reportOptions, options) => { - const msOptions = { - // unicode, html, or empty for no color - color: 'unicode', - metrics: [], - outputFile: 'coverage-summary.md', - ... reportOptions - }; - - const color = Util.normalizeColorType(msOptions.color); - - const mdPath = path.resolve(options.outputDir, msOptions.outputFile); - - const metrics = Util.getMetrics(msOptions.metrics, reportData.type); - - const rows = metrics.map((k) => { - return { - ... reportData.summary[k], - name: Util.capitalizeFirstLetter(k) - }; - }); - - const columns = getSummaryColumns(color); - - const markdownGrid = generateMarkdownGrid({ - options: { - name: reportData.name - }, - columns, - rows - }); - - await Util.writeFile(mdPath, markdownGrid); - return Util.relativePath(mdPath); -}; - -const handleRawReport = (reportData, reportOptions, options) => { - const rawOptions = { - outputDir: 'raw', - ... reportOptions - }; - - const cacheDir = options.cacheDir; - const rawDir = path.resolve(options.outputDir, rawOptions.outputDir); - // console.log(rawDir, cacheDir); - if (fs.existsSync(rawDir)) { - Util.logError(`Failed to save raw report because the dir already exists: ${Util.relativePath(rawDir)}`); - return; - } - - const rawParent = path.dirname(rawDir); - if (!fs.existsSync(rawParent)) { - fs.mkdirSync(rawParent, { - recursive: true - }); +// maybe v8 or to istanbul reports +const generateV8ListReports = async (v8list, coverageData, fileSources, options) => { + let istanbulReportPath; + // v8 to istanbul reports + if (options.reportGroup.istanbul) { + const istanbulCoverageResults = await saveIstanbulReports(coverageData, fileSources, options); + istanbulReportPath = istanbulCoverageResults.reportPath; } - // just rename the cache folder name - fs.renameSync(cacheDir, rawDir); - + // v8 reports and v8 coverage results + // could be no v8 or v8-json, but requires v8 coverage results + const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath); + return v8CoverageResults; }; - -// ======================================================================================================== - const getCoverageResults = async (dataList, sourceCache, options) => { // get first and check v8list or istanbul data const firstData = dataList[0]; @@ -595,25 +200,15 @@ const generateCoverageReports = async (dataList, sourceCache, options) => { // [ 'type', 'reportPath', 'name', 'watermarks', 'summary', 'files' ] // console.log(Object.keys(coverageResults)); - const buildInBothReports = { - 'codecov': handleCodecovReport, - 'codacy': handleCodacyReport, - 'console-details': handleConsoleDetailsReport, - 'console-summary': handleConsoleSummaryReport, - 'markdown-details': handleMarkdownDetailsReport, - 'markdown-summary': handleMarkdownSummaryReport, - 'raw': handleRawReport - }; - const bothGroup = options.reportGroup.both; if (bothGroup) { const bothReports = Object.keys(bothGroup); for (const reportName of bothReports) { const reportOptions = bothGroup[reportName]; - const buildInHandler = buildInBothReports[reportName]; + const builtInHandler = bothBuiltInReports[reportName]; const t1 = Date.now(); - if (buildInHandler) { - await buildInHandler(coverageResults, reportOptions, options); + if (builtInHandler) { + await builtInHandler(coverageResults, reportOptions, options); } else { await Util.runCustomReporter(reportName, coverageResults, reportOptions, options); } diff --git a/lib/index.d.ts b/lib/index.d.ts index 955a0154..c7f99d1c 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -129,6 +129,9 @@ declare namespace MCR { maxCols?: number; skipPercent?: number; metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">; + filter?: string | { + [pattern: string]: boolean; + } | ((row: any) => boolean); }] | ['markdown-summary'] | ['markdown-summary', { color: 'unicode' | 'html' | 'tex' | string; @@ -144,6 +147,9 @@ declare namespace MCR { maxCols?: number; skipPercent?: number; metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">; + filter?: string | { + [pattern: string]: boolean; + } | ((row: any) => boolean); /** * defaults to `coverage-details.md` */ diff --git a/lib/reports/codacy.js b/lib/reports/codacy.js new file mode 100644 index 00000000..9e71be4b --- /dev/null +++ b/lib/reports/codacy.js @@ -0,0 +1,43 @@ +const path = require('path'); +const Util = require('../utils/util.js'); + +const codacyReport = async (reportData, reportOptions, options) => { + const codacyOptions = { + outputFile: 'codacy.json', + ... reportOptions + }; + + const jsonPath = path.resolve(options.outputDir, codacyOptions.outputFile); + + // https://api.codacy.com/swagger#tocscoveragereport + const fileReports = []; + reportData.files.forEach((item) => { + const { sourcePath, data } = item; + + const coverage = {}; + for (const [key, value] of Object.entries(data.lines)) { + if (typeof value === 'number') { + coverage[key] = value; + } else { + // partial coverage not supported? + coverage[key] = 0; + } + } + + fileReports.push({ + filename: sourcePath, + coverage + }); + }); + const codacyData = { + fileReports + }; + + await Util.writeFile(jsonPath, JSON.stringify(codacyData)); + return Util.relativePath(jsonPath); +}; + + +module.exports = { + codacyReport +}; diff --git a/lib/reports/codecov.js b/lib/reports/codecov.js new file mode 100644 index 00000000..9eb37ea4 --- /dev/null +++ b/lib/reports/codecov.js @@ -0,0 +1,28 @@ +const path = require('path'); +const Util = require('../utils/util.js'); + +const codecovReport = async (reportData, reportOptions, options) => { + const codecovOptions = { + outputFile: 'codecov.json', + ... reportOptions + }; + + const jsonPath = path.resolve(options.outputDir, codecovOptions.outputFile); + + // https://docs.codecov.com/docs/codecov-custom-coverage-format + const coverage = {}; + reportData.files.forEach((item) => { + const { sourcePath, data } = item; + coverage[sourcePath] = data.lines; + }); + const codecovData = { + coverage + }; + + await Util.writeFile(jsonPath, JSON.stringify(codecovData)); + return Util.relativePath(jsonPath); +}; + +module.exports = { + codecovReport +}; diff --git a/lib/reports/console-details.js b/lib/reports/console-details.js new file mode 100644 index 00000000..d9884a15 --- /dev/null +++ b/lib/reports/console-details.js @@ -0,0 +1,125 @@ +const CG = require('console-grid'); +const EC = require('eight-colors'); +const { getGroupedRows } = require('../utils/snapshot.js'); +const Util = require('../utils/util.js'); + +const getRowData = (rowName, summary, metrics, color) => { + const summaryRow = {}; + let lowest = { + pct: 100, + status: 'high' + }; + metrics.map((k) => { + const s = summary[k]; + if (!s) { + return; + } + const percent = s.pct; + if (typeof percent !== 'number') { + return; + } + summaryRow[k] = Util.getColorStrByStatus(Util.PSF(percent, 100, 2), s.status, color); + if (percent < lowest.pct) { + lowest = s; + } + }); + summaryRow.nameStatus = lowest.status; + summaryRow.name = rowName; + return summaryRow; +}; + +const getDetailsRows = (files, metrics, cdOptions, color) => { + + const skipPercent = cdOptions.skipPercent; + if (typeof skipPercent === 'number' && skipPercent > 0) { + files = files.filter((file) => { + const { summary } = file; + for (const k of metrics) { + const percent = summary[k].pct; + if (typeof percent === 'number' && percent < skipPercent) { + return true; + } + } + return false; + }); + } + + const flatRows = []; + files.forEach((file) => { + + // do NOT add debug file + if (file.debug) { + return; + } + + const { sourcePath, summary } = file; + const fileRow = getRowData(sourcePath, summary, metrics, color); + fileRow.uncoveredLines = Util.getUncoveredLines(file.data.lines, color); + flatRows.push(fileRow); + }); + + return getGroupedRows(flatRows); +}; + + +const consoleDetailsReport = (reportData, reportOptions, options) => { + const cdOptions = { + maxCols: 50, + skipPercent: 0, + metrics: [], + ... reportOptions + }; + + const { + type, name, summary, files + } = reportData; + + if (name) { + Util.logInfo(EC.cyan(name)); + } + + const color = 'ansicode'; + + const metrics = Util.getMetrics(cdOptions.metrics, type); + const rows = getDetailsRows(files, metrics, cdOptions, color); + const summaryRow = getRowData('Summary', summary, metrics, color); + if (summaryRow.nameStatus) { + summaryRow.name = Util.getColorStrByStatus(summaryRow.name, summaryRow.nameStatus, color); + } + // no rows if skipped all by skipPercent + if (rows.length) { + rows.push({ + innerBorder: true + }); + } + rows.push(summaryRow); + + const columns = [{ + id: 'name', + name: 'Name' + }, ... metrics.map((m) => { + return { + id: m, + name: Util.capitalizeFirstLetter(m), + align: 'right' + }; + }), { + id: 'uncoveredLines', + name: 'Uncovered Lines' + }]; + + return CG({ + options: { + silent: cdOptions.silent, + nullPlaceholder: '', + defaultMaxWidth: cdOptions.maxCols + }, + columns, + rows + }); +}; + +module.exports = { + getRowData, + consoleDetailsReport +}; diff --git a/lib/reports/console-summary.js b/lib/reports/console-summary.js new file mode 100644 index 00000000..c737a53c --- /dev/null +++ b/lib/reports/console-summary.js @@ -0,0 +1,83 @@ +const CG = require('console-grid'); +const EC = require('eight-colors'); +const Util = require('../utils/util.js'); + +const nFormatter = (v) => { + if (typeof v === 'number') { + return Util.NF(v); + } + return v; +}; + + +const getSummaryColumns = (color) => { + + const columns = [{ + id: 'name', + name: 'Name' + }, { + id: 'pct', + name: 'Coverage %', + align: 'right', + formatter: (v, row, column) => { + if (typeof v === 'number') { + return Util.getColorStrByStatus(Util.PSF(v, 100, 2), row.status, color); + } + return v; + } + }, { + id: 'covered', + name: 'Covered', + align: 'right', + formatter: nFormatter + }, { + id: 'uncovered', + name: 'Uncovered', + align: 'right', + formatter: nFormatter + }, { + id: 'total', + name: 'Total', + align: 'right', + formatter: nFormatter + }]; + + return columns; +}; + +const consoleSummaryReport = (reportData, reportOptions, options) => { + + const csOptions = { + metrics: [], + ... reportOptions + }; + + const { + summary, name, type + } = reportData; + + if (name) { + Util.logInfo(EC.cyan(name)); + } + + const metrics = Util.getMetrics(csOptions.metrics, type); + + const rows = metrics.map((k) => { + return { + ... summary[k], + name: Util.capitalizeFirstLetter(k) + }; + }); + + const columns = getSummaryColumns('ansicode'); + + CG({ + columns, + rows + }); +}; + +module.exports = { + getSummaryColumns, + consoleSummaryReport +}; diff --git a/lib/reports/markdown-details.js b/lib/reports/markdown-details.js new file mode 100644 index 00000000..1928eecb --- /dev/null +++ b/lib/reports/markdown-details.js @@ -0,0 +1,118 @@ +const path = require('path'); +const generateMarkdownGrid = require('../utils/markdown.js'); +const Util = require('../utils/util.js'); + +const { getRowData } = require('./console-details.js'); + +const cutWithMaxCols = (str, maxCols, before) => { + if (str && maxCols && str.length > maxCols) { + if (before) { + return `... ${str.slice(str.length - (maxCols - 4))}`; + } + return `${str.slice(0, maxCols - 4)} ...`; + } + + return str; +}; + +const markdownDetailsReport = async (reportData, reportOptions, options) => { + const mdOptions = { + baseUrl: '', + // unicode, html, or empty for no color + color: 'unicode', + maxCols: 50, + skipPercent: 0, + metrics: [], + outputFile: 'coverage-details.md', + ... reportOptions + }; + + const baseUrl = mdOptions.baseUrl; + const color = Util.normalizeColorType(mdOptions.color); + const maxCols = Util.normalizeMaxCols(mdOptions.maxCols, 15); + + const mdPath = path.resolve(options.outputDir, mdOptions.outputFile); + + const metrics = Util.getMetrics(mdOptions.metrics, reportData.type); + + let files = reportData.files; + const skipPercent = mdOptions.skipPercent; + if (typeof skipPercent === 'number' && skipPercent > 0) { + files = files.filter((file) => { + const { summary } = file; + for (const k of metrics) { + const percent = summary[k].pct; + if (typeof percent === 'number' && percent < skipPercent) { + return true; + } + } + return false; + }); + } + + const rows = []; + files.forEach((file) => { + + // do NOT add debug file + if (file.debug) { + return; + } + + const { sourcePath, summary } = file; + + const fileRow = getRowData(sourcePath, summary, metrics, color); + + let rowName = cutWithMaxCols(fileRow.name, maxCols, true); + + if (fileRow.nameStatus) { + const nameColor = baseUrl && color === 'tex' ? '' : color; + rowName = Util.getColorStrByStatus(rowName, fileRow.nameStatus, nameColor); + } + if (baseUrl) { + rowName = `[${rowName}](${baseUrl + sourcePath})`; + } + + fileRow.name = rowName; + + const uncoveredLines = Util.getUncoveredLines(file.data.lines, color); + fileRow.uncoveredLines = cutWithMaxCols(uncoveredLines, maxCols); + + rows.push(fileRow); + }); + + const summaryRow = getRowData('Summary', reportData.summary, metrics, color); + if (summaryRow.nameStatus) { + summaryRow.name = Util.getColorStrByStatus(summaryRow.name, summaryRow.nameStatus, color); + } + rows.push(summaryRow); + + const columns = [{ + id: 'name', + name: 'Name' + }, ... metrics.map((m) => { + return { + id: m, + name: Util.capitalizeFirstLetter(m), + align: 'right' + }; + }), { + id: 'uncoveredLines', + name: 'Uncovered Lines' + }]; + + const markdownGrid = generateMarkdownGrid({ + options: { + name: reportData.name + }, + columns, + rows + }); + + await Util.writeFile(mdPath, markdownGrid); + return Util.relativePath(mdPath); +}; + + +module.exports = { + markdownDetailsReport +}; diff --git a/lib/reports/markdown-summary.js b/lib/reports/markdown-summary.js new file mode 100644 index 00000000..e918b790 --- /dev/null +++ b/lib/reports/markdown-summary.js @@ -0,0 +1,45 @@ +const path = require('path'); +const generateMarkdownGrid = require('../utils/markdown.js'); +const Util = require('../utils/util.js'); + +const { getSummaryColumns } = require('./console-summary.js'); + +const markdownSummaryReport = async (reportData, reportOptions, options) => { + const msOptions = { + // unicode, html, or empty for no color + color: 'unicode', + metrics: [], + outputFile: 'coverage-summary.md', + ... reportOptions + }; + + const color = Util.normalizeColorType(msOptions.color); + + const mdPath = path.resolve(options.outputDir, msOptions.outputFile); + + const metrics = Util.getMetrics(msOptions.metrics, reportData.type); + + const rows = metrics.map((k) => { + return { + ... reportData.summary[k], + name: Util.capitalizeFirstLetter(k) + }; + }); + + const columns = getSummaryColumns(color); + + const markdownGrid = generateMarkdownGrid({ + options: { + name: reportData.name + }, + columns, + rows + }); + + await Util.writeFile(mdPath, markdownGrid); + return Util.relativePath(mdPath); +}; + +module.exports = { + markdownSummaryReport +}; diff --git a/lib/reports/raw.js b/lib/reports/raw.js new file mode 100644 index 00000000..459aaf5a --- /dev/null +++ b/lib/reports/raw.js @@ -0,0 +1,33 @@ +const fs = require('fs'); +const path = require('path'); +const Util = require('../utils/util.js'); + +const rawReport = (reportData, reportOptions, options) => { + const rawOptions = { + outputDir: 'raw', + ... reportOptions + }; + + const cacheDir = options.cacheDir; + const rawDir = path.resolve(options.outputDir, rawOptions.outputDir); + // console.log(rawDir, cacheDir); + if (fs.existsSync(rawDir)) { + Util.logError(`Failed to save raw report because the dir already exists: ${Util.relativePath(rawDir)}`); + return; + } + + const rawParent = path.dirname(rawDir); + if (!fs.existsSync(rawParent)) { + fs.mkdirSync(rawParent, { + recursive: true + }); + } + + // just rename the cache folder name + fs.renameSync(cacheDir, rawDir); + +}; + +module.exports = { + rawReport +}; diff --git a/test/test-pr.js b/test/test-pr.js index 07f2b849..967c3749 100644 --- a/test/test-pr.js +++ b/test/test-pr.js @@ -51,6 +51,10 @@ const test = async () => { const prChanges = await getPullRequestChanges(); console.log('prChanges', prChanges); + const filter = (row) => { + console.log(row); + }; + const coverageOptions = { // logging: 'debug', cleanCache: true, @@ -61,7 +65,8 @@ const test = async () => { ['console-details', { // skipPercent: 100, metrics: ['bytes', 'lines'], - maxCols: 30 + maxCols: 30, + filter }], ['v8', { // metrics: ['bytes', 'functions', 'lines'] @@ -72,7 +77,8 @@ const test = async () => { ['markdown-details', { // color: 'Tex', baseUrl: 'https://cenfun.github.io/monocart-coverage-reports/pr/#page=', - metrics: ['bytes', 'lines'] + metrics: ['bytes', 'lines'], + filter }] ], @@ -81,16 +87,10 @@ const test = async () => { outputDir: './docs/pr', - sourceFilter: (sourcePath) => { - if (prChanges.length) { - for (const file of prChanges) { - if (sourcePath.includes(file)) { - return true; - } - } - } - return false; + sourceFilter: { + '**/src/**': true } + }; const mcr = new CoverageReport(coverageOptions);