istanbuljs / v8-to-istanbul

convert from v8 coverage format to istanbul's format
ISC License
115 stars 40 forks source link

`wrapperLength` should not be applied to empty coverage #209

Closed AriPerkkio closed 1 year ago

AriPerkkio commented 1 year ago

Description

The end.column may be out of range when wrapperLength is applied to empty coverage reports. E.g. "end": { "line": 7, "column": -229 }

The empty coverage reports are typically (always?) manually generated. Utilizers read the raw sources from file system and create fake V8 reports for these uncovered files. Example from c8:

https://github.com/bcoe/c8/blob/2f36fe9d00bf2f3b869ce890ed11416304c903fe/lib/report.js#L203-L220

When the utilizers have a shared wrapperLength that they apply to all V8 reports, the empty coverage ones may be invalid as they did not contain the expected wrapper around source codes. This bug comes up especially in c8 but the fixed could be set here in upstream so that other utilizers wouldn't have to do same checks. @bcoe any thoughts - should this be instead in c8 only?

The fix should not cause any conflicts even if some utilizer is already taking this into consideration.

Minimal reproduction

import v8ToIstanbul from "v8-to-istanbul";
import { writeFileSync } from "node:fs";

const source = `
function uncoveredFunction() {
  return "Hello world";
}

if ("Branches here" === false) {
  uncoveredFunction();
}`.trim();

writeFileSync("test-file.js", source, "utf-8");

const wrapperLength = source.length * 2;
const converter = v8ToIstanbul("./test-file.js", wrapperLength);

await converter.load();

converter.applyCoverage([
  {
    functionName: "(empty-report)",
    ranges: [
      {
        startOffset: 0,
        endOffset: source.length,
        count: 0,
      },
    ],
    isBlockCoverage: true,
  },
]);

const coverageMap = converter.toIstanbul();
const [fileCoverage] = Object.values(coverageMap);
const { branchMap, fnMap } = fileCoverage;

console.log(JSON.stringify({ branchMap, fnMap }, null, 2));
{
  "branchMap": {
    "0": {
      "type": "branch",
      "line": 1,
      "loc": { 
        "start": { "line": 1, "column": 0 },
        "end": { "line": 7, "column": -229 } // <- Negative column
      },
      "locations": [
        {
          "start": { "line": 1, "column": 0 },
          "end": { "line": 7, "column": -229 } <- Negative column
        }
      ]
    }
  },
  "fnMap": {
    "0": {
      "name": "(empty-report)",
      "decl": {
        "start": { "line": 1, "column": 0 },
        "end": { "line": 7, "column": -229 } <- Negative column
      },
      "loc": {
        "start": { "line": 1, "column": 0 },
        "end": { "line": 7, "column": -229 } <- Negative column
      },
      "line": 1
    }
  }
}