Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

istanbul-lib-source-maps does not preserve implicit else #705

Open
AriPerkkio opened this issue Nov 4, 2022 · 0 comments · May be fixed by #706
Open

istanbul-lib-source-maps does not preserve implicit else #705

AriPerkkio opened this issue Nov 4, 2022 · 0 comments · May be fixed by #706

Comments

@AriPerkkio
Copy link
Contributor

When the coverage map is mapped back to sources using istanbul-lib-source-maps the implicit else gets dropped in the process. Here is the line that checks whether the branch exists in sources, and simply excludes it from results. Since the implicit else does not exist in the sources, the mapping variable is falsy.

branchMeta.locations.forEach((loc, i) => {
const mapping = this.resolveMapping(sourceMap, loc, fc.path);
if (mapping) {
if (!source) {
source = mapping.source;
}
if (mapping.source !== source) {
skip = true;
}
locs.push(mapping.loc);
mappedHits.push(hits[i]);
}
});

Example of implicit else just so that everyone is on the track of what this issue is about:

// Source code
if(condition) {
  method();
}
unrelatedMethod();

// Instrumented
if(condition) {
  coverageMap.b[0]++;
  method();
} else { // Implicit else right here!
  coverageMap.b[1]++;
}
unrelatedMethod();

Check the reproduction case below to see how data.branchMap and data.b loses the else branch.

Minimal reproduction case

Requirements:

$ npm install istanbul-lib-instrument istanbul-lib-coverage istanbul-lib-source-maps esbuild
$ node -v
> v16.15.1
const filename = "/math.ts";

function logBranches(label, coverageMap) {
  console.log(label);
  console.log(JSON.stringify(coverageMap.data[filename].branchMap, null, 2));
  console.log(coverageMap.data[filename].b);
}

async function run() {
  const sourceCode = `
function add(a: number, b: number): number {
    type SomeType = typeof a | string;

    console.log('Running add');

    if(a === 20) {
        return b;
    }
}

add(1, 2);
`;

  const transpiled = require("esbuild").transformSync(sourceCode, {
    loader: "ts",
    sourcemap: true,
    sourcefile: filename,
  });
  transpiled.map = JSON.parse(transpiled.map);

  const instrumenter = require("istanbul-lib-instrument").createInstrumenter({
    produceSourceMap: true,
    autoWrap: false,
    compact: false,
    coverageVariable: "__coverage__",
    coverageGlobalScope: "globalThis",
  });

  const instrumented = instrumenter.instrumentSync(
    transpiled.code,
    filename,
    transpiled.map
  );

  // Run instrumented code to populate globalThis.__coverage__
  eval(instrumented);
  const __coverage__ = globalThis.__coverage__;

  const { createCoverageMap } = require("istanbul-lib-coverage");
  const coverageMap = createCoverageMap(__coverage__);

  const { createSourceMapStore } = require("istanbul-lib-source-maps");
  const sourceMapStore = createSourceMapStore();
  const transformedCoverage = await sourceMapStore.transformCoverage(
    coverageMap
  );

  logBranches("coveragemap before transforming back to sources", coverageMap);
  logBranches("coveragemap after transforming to sources", transformedCoverage);
}

run();

Output:

$ node index.js 
Running add
coveragemap before transforming back to sources
{
  "0": {
    "loc": {
      "start": {
        "line": 3,
        "column": 2
      },
      "end": {
        "line": 5,
        "column": 3
      }
    },
    "type": "if",
    "locations": [
      {
        "start": {
          "line": 3,
          "column": 2
        },
        "end": {
          "line": 5,
          "column": 3
        }
      },
      {  // See here!
        "start": {},
        "end": {}
      }
    ],
    "line": 3
  }
}
{ '0': [ 0, 1 ] }  // See here!

coveragemap after transforming to sources
{
  "0": {
    "loc": {
      "start": {
        "line": 7,
        "column": 4
      },
      "end": {
        "line": 9,
        "column": null
      }
    },
    "type": "if",
    "locations": [
      {
        "start": {
          "line": 7,
          "column": 4
        },
        "end": {
          "line": 9,
          "column": null
        }
      }
       // See here!
    ]
  }
}
{ '0': [ 0 ] } // See here!

Related issues from downstream:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant