@code-pushup/coverage-plugin 0.44.2 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  🧪 **Code PushUp plugin for tracking code coverage.** ☂️
8
8
 
9
9
  This plugin allows you to measure and track code coverage on your project.
10
+ It accepts the LCOV coverage format and merges coverage results from any test suites provided.
10
11
 
11
12
  Measured coverage types are mapped to Code PushUp audits in the following way
12
13
 
@@ -213,7 +214,11 @@ For instance, the following can be an audit output for line coverage.
213
214
  }
214
215
  ```
215
216
 
216
- ### Providing coverage results in Nx monorepo
217
+ ### Coverage results alteration
218
+
219
+ At the moment, the LCOV results include `(empty-report)` functions with missing coverage. These point to various imports or exports, not actual functions. For that reason, they are omitted from the results.
220
+
221
+ ## Providing coverage results in Nx monorepo
217
222
 
218
223
  As a part of the plugin, there is a `getNxCoveragePaths` helper for setting up paths to coverage results if you are using Nx. The helper accepts all relevant targets (e.g. `test` or `unit-test`) and searches for a coverage path option.
219
224
  Jest and Vitest configuration options are currently supported:
package/bin.js CHANGED
@@ -1156,10 +1156,134 @@ var PLUGIN_CONFIG_PATH = join2(
1156
1156
  WORKDIR,
1157
1157
  "plugin-config.json"
1158
1158
  );
1159
+ var INVALID_FUNCTION_NAME = "(empty-report)";
1159
1160
 
1160
1161
  // packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts
1161
1162
  import { join as join3 } from "node:path";
1162
1163
 
1164
+ // packages/plugin-coverage/src/lib/runner/lcov/merge-lcov.ts
1165
+ function mergeLcovResults(records) {
1166
+ const allFilenames = records.map((record) => record.file);
1167
+ if (allFilenames.length === new Set(allFilenames).size) {
1168
+ return records;
1169
+ }
1170
+ return records.reduce((accMerged, currRecord, currIndex) => {
1171
+ const filePath = currRecord.file;
1172
+ const lines6 = currRecord.lines.found;
1173
+ const duplicates = records.reduce(
1174
+ (acc, candidateRecord, candidateIndex) => {
1175
+ if (candidateRecord.file === filePath && candidateRecord.lines.found === lines6 && candidateIndex !== currIndex) {
1176
+ return [...acc, [candidateRecord, candidateIndex]];
1177
+ }
1178
+ return acc;
1179
+ },
1180
+ []
1181
+ );
1182
+ if (duplicates.map((duplicate) => duplicate[1]).some((index) => index < currIndex)) {
1183
+ return accMerged;
1184
+ }
1185
+ if (duplicates.length === 0) {
1186
+ return [...accMerged, currRecord];
1187
+ }
1188
+ return [
1189
+ ...accMerged,
1190
+ mergeDuplicateLcovRecords([
1191
+ currRecord,
1192
+ ...duplicates.map((duplicate) => duplicate[0])
1193
+ ])
1194
+ ];
1195
+ }, []);
1196
+ }
1197
+ function mergeDuplicateLcovRecords(records) {
1198
+ const linesDetails = mergeLcovLineDetails(
1199
+ records.map((record) => record.lines.details)
1200
+ );
1201
+ const linesHit = linesDetails.reduce(
1202
+ (acc, line) => acc + (line.hit > 0 ? 1 : 0),
1203
+ 0
1204
+ );
1205
+ const branchesDetails = mergeLcovBranchesDetails(
1206
+ records.map((record) => record.branches.details)
1207
+ );
1208
+ const branchesHit = branchesDetails.reduce(
1209
+ (acc, branch) => acc + (branch.taken > 0 ? 1 : 0),
1210
+ 0
1211
+ );
1212
+ const functionsDetails = mergeLcovFunctionsDetails(
1213
+ records.map((record) => record.functions.details)
1214
+ );
1215
+ const functionsHit = functionsDetails.reduce(
1216
+ (acc, func) => acc + (func.hit != null && func.hit > 0 ? 1 : 0),
1217
+ 0
1218
+ );
1219
+ const mergedRecord = {
1220
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1221
+ file: records[0].file,
1222
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1223
+ title: records[0].title,
1224
+ lines: {
1225
+ found: linesDetails.length,
1226
+ hit: linesHit,
1227
+ details: linesDetails
1228
+ },
1229
+ branches: {
1230
+ found: branchesDetails.length,
1231
+ hit: branchesHit,
1232
+ details: branchesDetails
1233
+ },
1234
+ functions: {
1235
+ found: functionsDetails.length,
1236
+ hit: functionsHit,
1237
+ details: functionsDetails
1238
+ }
1239
+ };
1240
+ return mergedRecord;
1241
+ }
1242
+ function mergeLcovLineDetails(details4) {
1243
+ const flatDetails = details4.flat();
1244
+ const uniqueLines = [
1245
+ ...new Set(flatDetails.map((flatDetail) => flatDetail.line))
1246
+ ];
1247
+ return uniqueLines.map((line) => {
1248
+ const hitSum = flatDetails.filter((lineDetail) => lineDetail.line === line).reduce((acc, lineDetail) => acc + lineDetail.hit, 0);
1249
+ return { line, hit: hitSum };
1250
+ });
1251
+ }
1252
+ function mergeLcovBranchesDetails(details4) {
1253
+ const flatDetails = details4.flat();
1254
+ const uniqueBranches = [
1255
+ ...new Set(
1256
+ flatDetails.map(
1257
+ ({ line, block, branch }) => JSON.stringify({ line, block, branch })
1258
+ )
1259
+ )
1260
+ ].map(
1261
+ (functionJSON) => JSON.parse(functionJSON)
1262
+ );
1263
+ return uniqueBranches.map(({ line, block, branch }) => {
1264
+ const takenSum = flatDetails.filter(
1265
+ (branchDetail) => branchDetail.line === line && branchDetail.block === block && branchDetail.branch === branch
1266
+ ).reduce((acc, branchDetail) => acc + branchDetail.taken, 0);
1267
+ return { line, block, branch, taken: takenSum };
1268
+ });
1269
+ }
1270
+ function mergeLcovFunctionsDetails(details4) {
1271
+ const flatDetails = details4.flat();
1272
+ const uniqueFunctions = [
1273
+ ...new Set(
1274
+ flatDetails.map(({ line, name }) => JSON.stringify({ line, name }))
1275
+ )
1276
+ ].map(
1277
+ (functionJSON) => JSON.parse(functionJSON)
1278
+ );
1279
+ return uniqueFunctions.map(({ line, name }) => {
1280
+ const hitSum = flatDetails.filter(
1281
+ (functionDetail) => functionDetail.line === line && functionDetail.name === name
1282
+ ).reduce((acc, functionDetail) => acc + (functionDetail.hit ?? 0), 0);
1283
+ return { line, name, hit: hitSum };
1284
+ });
1285
+ }
1286
+
1163
1287
  // packages/plugin-coverage/src/lib/runner/lcov/parse-lcov.ts
1164
1288
  import parseLcovExport from "parse-lcov";
1165
1289
  var godKnows = parseLcovExport;
@@ -1184,21 +1308,41 @@ function mergeConsecutiveNumbers(numberArr) {
1184
1308
 
1185
1309
  // packages/plugin-coverage/src/lib/runner/lcov/transform.ts
1186
1310
  function lcovReportToFunctionStat(record) {
1311
+ const validRecord = removeEmptyReport(record);
1187
1312
  return {
1188
- totalFound: record.functions.found,
1189
- totalHit: record.functions.hit,
1190
- issues: record.functions.hit < record.functions.found ? record.functions.details.filter((detail) => !detail.hit).map(
1313
+ totalFound: validRecord.functions.found,
1314
+ totalHit: validRecord.functions.hit,
1315
+ issues: validRecord.functions.hit < validRecord.functions.found ? validRecord.functions.details.filter((detail) => !detail.hit).map(
1191
1316
  (detail) => ({
1192
1317
  message: `Function ${detail.name} is not called in any test case.`,
1193
1318
  severity: "error",
1194
1319
  source: {
1195
- file: record.file,
1320
+ file: validRecord.file,
1196
1321
  position: { startLine: detail.line }
1197
1322
  }
1198
1323
  })
1199
1324
  ) : []
1200
1325
  };
1201
1326
  }
1327
+ function removeEmptyReport(record) {
1328
+ const validFunctions = record.functions.details.filter(
1329
+ (detail) => detail.name !== INVALID_FUNCTION_NAME
1330
+ );
1331
+ if (validFunctions.length === record.functions.found) {
1332
+ return record;
1333
+ }
1334
+ return {
1335
+ ...record,
1336
+ functions: {
1337
+ details: validFunctions,
1338
+ found: validFunctions.length,
1339
+ hit: validFunctions.reduce(
1340
+ (acc, fn) => acc + (fn.hit != null && fn.hit > 0 ? 1 : 0),
1341
+ 0
1342
+ )
1343
+ }
1344
+ };
1345
+ }
1202
1346
  function lcovReportToLineStat(record) {
1203
1347
  const missingCoverage = record.lines.hit < record.lines.found;
1204
1348
  const lines6 = missingCoverage ? record.lines.details.filter((detail) => !detail.hit).map((detail) => detail.line) : [];
@@ -1263,8 +1407,9 @@ function lcovCoverageToAuditOutput(stat2, coverageType) {
1263
1407
  // packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts
1264
1408
  async function lcovResultsToAuditOutputs(results, coverageTypes) {
1265
1409
  const lcovResults = await parseLcovFiles(results);
1410
+ const mergedResults = mergeLcovResults(lcovResults);
1266
1411
  const totalCoverageStats = getTotalCoverageFromLcovRecords(
1267
- lcovResults,
1412
+ mergedResults,
1268
1413
  coverageTypes
1269
1414
  );
1270
1415
  return coverageTypes.map((coverageType) => {
package/index.js CHANGED
@@ -1081,7 +1081,7 @@ import chalk4 from "chalk";
1081
1081
 
1082
1082
  // packages/plugin-coverage/package.json
1083
1083
  var name = "@code-pushup/coverage-plugin";
1084
- var version = "0.44.2";
1084
+ var version = "0.45.0";
1085
1085
 
1086
1086
  // packages/plugin-coverage/src/lib/config.ts
1087
1087
  import { z as z16 } from "zod";
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@code-pushup/coverage-plugin",
3
- "version": "0.44.2",
3
+ "version": "0.45.0",
4
4
  "dependencies": {
5
- "@code-pushup/models": "0.44.2",
6
- "@code-pushup/utils": "0.44.2",
5
+ "@code-pushup/models": "0.45.0",
6
+ "@code-pushup/utils": "0.45.0",
7
7
  "parse-lcov": "^1.0.4",
8
8
  "chalk": "^5.3.0",
9
9
  "zod": "^3.22.4"
@@ -1,3 +1,4 @@
1
1
  export declare const WORKDIR: string;
2
2
  export declare const RUNNER_OUTPUT_PATH: string;
3
3
  export declare const PLUGIN_CONFIG_PATH: string;
4
+ export declare const INVALID_FUNCTION_NAME = "(empty-report)";
@@ -0,0 +1,6 @@
1
+ import { BranchesDetails, FunctionsDetails, LCOVRecord, LinesDetails } from 'parse-lcov';
2
+ export declare function mergeLcovResults(records: LCOVRecord[]): LCOVRecord[];
3
+ export declare function mergeDuplicateLcovRecords(records: LCOVRecord[]): LCOVRecord;
4
+ export declare function mergeLcovLineDetails(details: LinesDetails[][]): LinesDetails[];
5
+ export declare function mergeLcovBranchesDetails(details: BranchesDetails[][]): BranchesDetails[];
6
+ export declare function mergeLcovFunctionsDetails(details: FunctionsDetails[][]): FunctionsDetails[];