AST-aware v8-to-istanbul.
Unopinionated - bring-your-own AST parser and source maps.
Passes all 195 tests* of istanbul-lib-instrument. 
Test cases run against:
vite/parseAst 
acorn 
oxc-parser 
@babel/parser 
See example report at https://ariperkkio.github.io/ast-v8-to-istanbul.
import { convert } from "ast-v8-to-istanbul";
import { parseAstAsync } from "vite";
import type { CoverageMapData } from "istanbul-lib-coverage";
const data: CoverageMapData = await convert({
ast: parseAstAsync(<code>),
code: "function sum(a, b) {\n return a + b ...",
wrapperLength: 0,
coverage: {
scriptId: "123",
url: "file:///absolute/path/to/dist/index.js",
functions: [
{
functionName: "sum",
ranges: [{ startOffset: 223, endOffset: 261, count: 0 }],
isBlockCoverage: false,
},
],
},
sourceMap: {
version: 3,
sources: ["../sources.ts"],
sourcesContent: ["export function sum(a: number, b: number) {\n..."],
mappings: ";AAAO,SAAS,...",
names: [],
},
});
Ignoring code
Ignoring source code
Ignore hints
See live example at https://ariperkkio.github.io/ast-v8-to-istanbul/ignore-examples.html.
The typical ignore hints from nyc are supported: https://github.com/istanbuljs/nyc?tab=readme-ov-file#parsing-hints-ignoring-lines:
/* istanbul ignore if */: ignore the next if statement.
/* istanbul ignore else */: ignore the else portion of an if statement.
/* istanbul ignore next */: ignore the next thing in the source-code (functions, if statements, classes, you name it).
/* istanbul ignore file */: ignore an entire source-file (this should be placed at the top of the file).
In addition to istanbul keyword, you can use v8, c8 and node:coverage:
/* istanbul ignore if */
/* v8 ignore else */
/* c8 ignore file */
/* node:coverage ignore next */
Also start and stop ignore hints from original v8-to-istanbul are supported.
These ignore hints are checked from the original sources instead of transpiled code.
/* v8 ignore start */: start ignoring lines
/* v8 ignore stop */: stop ignoring lines
<!-- /* v8 ignore start */ -->: start ignoring lines
<!-- /* v8 ignore stop */ -->: stop ignoring lines
anything /* v8 ignore start */ anything: start ignoring lines
anything /* v8 ignore stop */ anything: stop ignoring lines
Class methods
The ignore-class-method from nyc is also supported: https://github.com/istanbuljs/nyc?tab=readme-ov-file#ignoring-methods
You can ignore every instance of a method simply by adding its name to the ignore-class-method array in your nyc config.
import { convert } from "ast-v8-to-istanbul";
await convert({
ignoreClassMethods: ['render']
});
Ignore after remapping
You can ignore source code after coverage results have been remapped back to original sources using ignoreSourceCode.
This is a high level API that can be exposed to end-users by tooling developers.
It's mostly intended for excluding code that is incorrectly shown in coverage report when compilers add generated code in the source maps.
Note that as the exclusion happens after remapping, this option is slower than ignoreNode option.
function ignoreSourceCode(
code: string,
type: "function" | "statement" | "branch",
location: Record<"start" | "end", { line: number; column: number }>,
): boolean | void;
import { convert } from "ast-v8-to-istanbul";
await convert({
ignoreSourceCode: (code, type, location) => {
if(type === "function" && code.includes("noop(")) {
return true;
}
if(code === '<script>') {
return true;
}
return location.start.line < 5;
},
});
Ignoring generated code
This API is mostly for developers integrating ast-v8-to-istanbul with other tooling.
If your code transform pipeline is adding generated code that's included in the source maps, it will be included in coverage too.
You can exclude these known patterns by defining ignoreNode for filtering such nodes.
By returning "ignore-this-and-nested-nodes" from the handler, you can ignore all nested nodes too.
This can be useful when you need to ignore everything a certain node wraps, e.g. IfStatement.
function ignoreNode(
node: Node,
type: "branch" | "function" | "statement"
): boolean | "ignore-this-and-nested-nodes" | void;
import { convert } from "ast-v8-to-istanbul";
await convert({
ignoreNode: (node, type) => {
return (
type === "statement" &&
node.type === "AwaitExpression" &&
node.argument.type === "CallExpression" &&
node.argument.callee.type === "Identifier" &&
node.argument.callee.name === "__vite_ssr_import__"
);
},
});
Source maps
Source maps are optional and supported by various ways:
- Pass directly to
convert as argument:import { convert } from "ast-v8-to-istanbul";
await convert({
sourceMap: {
version: 3,
sources: ["../sources.ts"],
sourcesContent: ["export function sum(a: number, b: number) {\n..."],
mappings: ";AAAO,SAAS,...",
names: [],
}
});
- Include base64 encoded inline maps in
code:await convert({
code: `\
function hello() {}
//# sourceMappingURL=data:application/json;base64,eyJzb3VyY2VzIjpbIi9zb21lL...
`
});
- Include inline maps with filename in
code:await convert({
code: `\
function hello() {}
//# sourceMappingURL=some-file-on-file-system.js.map
`
});
- Don't use source maps at all, and pass original source code in
code:await convert({
code: `function hello() {}`,
sourceMap: undefined,
});
Istanbul Compatibility
This project tests itself against test cases of istanbul-lib-instrument and verifies coverage maps are 100% identical. Some cases, like deprecated with() statement and edge cases of strict mode are skipped, as all tests are run in strict mode.
100% istanbul compatibility guarantees that coverage reports between V8 and Istanbul can be merged together.
Limitations
The way how V8 reports runtime coverage has some limitations when compared to pre-instrumented coverage: