Socket
Socket
Sign inDemoInstall

@vitest/coverage-v8

Package Overview
Dependencies
Maintainers
3
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vitest/coverage-v8 - npm Package Compare versions

Comparing version 0.34.6 to 1.0.4

1

dist/index.d.ts
import { V8CoverageProvider } from './provider.js';
import 'node:inspector';
import 'vitest/coverage';

@@ -4,0 +3,0 @@ import 'vitest';

12

dist/provider.d.ts

@@ -1,2 +0,1 @@

import { Profiler } from 'node:inspector';
import { BaseCoverageProvider } from 'vitest/coverage';

@@ -20,2 +19,6 @@ import { CoverageProvider, AfterSuiteRunMeta, ReportContext, ResolvedCoverageOptions } from 'vitest';

type Options = ResolvedCoverageOptions<'v8'>;
type Filename = string;
type CoverageFilesByTransformMode = Record<AfterSuiteRunMeta['transformMode'], Filename[]>;
type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT;
declare const DEFAULT_PROJECT: unique symbol;
declare class V8CoverageProvider extends BaseCoverageProvider implements CoverageProvider {

@@ -26,12 +29,15 @@ name: string;

testExclude: InstanceType<TestExclude>;
coverages: Profiler.TakePreciseCoverageReturnType[];
coverageFiles: Map<ProjectName, CoverageFilesByTransformMode>;
coverageFilesDirectory: string;
pendingPromises: Promise<void>[];
initialize(ctx: Vitest): void;
resolveOptions(): Options;
clean(clean?: boolean): Promise<void>;
onAfterSuiteRun({ coverage }: AfterSuiteRunMeta): void;
onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta): void;
reportCoverage({ allTestsRun }?: ReportContext): Promise<void>;
private getUntestedFiles;
private getSources;
private convertCoverage;
}
export { V8CoverageProvider };

@@ -1,3 +0,3 @@

import { existsSync, promises } from 'fs';
import { fileURLToPath, pathToFileURL } from 'url';
import { existsSync, promises, writeFileSync } from 'node:fs';
import { pathToFileURL, fileURLToPath } from 'node:url';
import v8ToIstanbul from 'v8-to-istanbul';

@@ -10,6 +10,8 @@ import { mergeProcessCovs } from '@bcoe/v8-coverage';

import MagicString from 'magic-string';
import { parseModule } from 'magicast';
import remapping from '@ampproject/remapping';
import c from 'picocolors';
import { provider } from 'std-env';
import { builtinModules } from 'module';
import createDebug from 'debug';
import { builtinModules } from 'node:module';
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config';

@@ -172,2 +174,5 @@ import { BaseCoverageProvider } from 'vitest/coverage';

const VITE_EXPORTS_LINE_PATTERN = /Object\.defineProperty\(__vite_ssr_exports__.*\n/g;
const DEFAULT_PROJECT = Symbol.for("default-project");
const debug = createDebug("vitest:coverage");
let uniqueId = 0;
class V8CoverageProvider extends BaseCoverageProvider {

@@ -178,3 +183,5 @@ name = "v8";

testExclude;
coverages = [];
coverageFiles = /* @__PURE__ */ new Map();
coverageFilesDirectory;
pendingPromises = [];
initialize(ctx) {

@@ -191,6 +198,9 @@ const config = ctx.config.coverage;

reportsDirectory: resolve(ctx.config.root, config.reportsDirectory || coverageConfigDefaults.reportsDirectory),
lines: config["100"] ? 100 : config.lines,
functions: config["100"] ? 100 : config.functions,
branches: config["100"] ? 100 : config.branches,
statements: config["100"] ? 100 : config.statements
thresholds: config.thresholds && {
...config.thresholds,
lines: config.thresholds["100"] ? 100 : config.thresholds.lines,
branches: config.thresholds["100"] ? 100 : config.thresholds.branches,
functions: config.thresholds["100"] ? 100 : config.thresholds.functions,
statements: config.thresholds["100"] ? 100 : config.thresholds.statements
}
};

@@ -205,2 +215,3 @@ this.testExclude = new _TestExclude({

});
this.coverageFilesDirectory = resolve(this.options.reportsDirectory, ".tmp");
}

@@ -213,6 +224,25 @@ resolveOptions() {

await promises.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 });
this.coverages = [];
if (existsSync(this.coverageFilesDirectory))
await promises.rm(this.coverageFilesDirectory, { recursive: true, force: true, maxRetries: 10 });
await promises.mkdir(this.coverageFilesDirectory, { recursive: true });
this.coverageFiles = /* @__PURE__ */ new Map();
this.pendingPromises = [];
}
onAfterSuiteRun({ coverage }) {
this.coverages.push(coverage);
/*
* Coverage and meta information passed from Vitest runners.
* Note that adding new entries here and requiring on those without
* backwards compatibility is a breaking change.
*/
onAfterSuiteRun({ coverage, transformMode, projectName }) {
if (transformMode !== "web" && transformMode !== "ssr")
throw new Error(`Invalid transform mode: ${transformMode}`);
let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT);
if (!entry) {
entry = { web: [], ssr: [] };
this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry);
}
const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`);
entry[transformMode].push(filename);
const promise = promises.writeFile(filename, JSON.stringify(coverage), "utf-8");
this.pendingPromises.push(promise);
}

@@ -222,29 +252,35 @@ async reportCoverage({ allTestsRun } = {}) {

this.ctx.logger.log(c.blue(" % ") + c.yellow("@vitest/coverage-v8 does not work on Stackblitz. Report will be empty."));
const transformResults = normalizeTransformResults(this.ctx.projects.map((project) => project.vitenode.fetchCache));
const merged = mergeProcessCovs(this.coverages);
const scriptCoverages = merged.result.filter((result) => this.testExclude.shouldInstrument(fileURLToPath(result.url)));
const coverageMap = libCoverage.createCoverageMap({});
let index = 0;
const total = this.pendingPromises.length;
await Promise.all(this.pendingPromises);
this.pendingPromises = [];
for (const [projectName, coveragePerProject] of this.coverageFiles.entries()) {
for (const [transformMode, filenames] of Object.entries(coveragePerProject)) {
let merged = { result: [] };
for (const chunk of toSlices(filenames, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length;
debug("Covered files %d/%d", index, total);
}
await Promise.all(chunk.map(async (filename) => {
const contents = await promises.readFile(filename, "utf-8");
const coverage = JSON.parse(contents);
merged = mergeProcessCovs([merged, coverage]);
}));
}
const converted = await this.convertCoverage(merged, projectName, transformMode);
const transformedCoverage = await transformCoverage(converted);
coverageMap.merge(transformedCoverage);
}
}
if (this.options.all && allTestsRun) {
const coveredFiles = Array.from(scriptCoverages.map((r) => r.url));
const untestedFiles = await this.getUntestedFiles(coveredFiles, transformResults);
scriptCoverages.push(...untestedFiles);
const coveredFiles = coverageMap.files();
const untestedCoverage = await this.getUntestedFiles(coveredFiles);
const converted = await this.convertCoverage(untestedCoverage);
coverageMap.merge(await transformCoverage(converted));
}
const converted = await Promise.all(scriptCoverages.map(async ({ url, functions }) => {
const sources = await this.getSources(url, transformResults, functions);
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0;
const converter = v8ToIstanbul(url, wrapperLength, sources);
await converter.load();
converter.applyCoverage(functions);
return converter.toIstanbul();
}));
const mergedCoverage = converted.reduce((coverage, previousCoverageMap) => {
const map = libCoverage.createCoverageMap(coverage);
map.merge(previousCoverageMap);
return map;
}, libCoverage.createCoverageMap({}));
const sourceMapStore = libSourceMaps.createSourceMapStore();
const coverageMap = await sourceMapStore.transformCoverage(mergedCoverage);
const context = libReport.createContext({
dir: this.options.reportsDirectory,
coverageMap,
sourceFinder: sourceMapStore.sourceFinder,
watermarks: this.options.watermarks

@@ -261,57 +297,70 @@ });

}
if (this.options.branches || this.options.functions || this.options.lines || this.options.statements) {
this.checkThresholds({
if (this.options.thresholds) {
const resolvedThresholds = this.resolveThresholds({
coverageMap,
thresholds: {
branches: this.options.branches,
functions: this.options.functions,
lines: this.options.lines,
statements: this.options.statements
},
perFile: this.options.perFile
thresholds: this.options.thresholds,
createCoverageMap: () => libCoverage.createCoverageMap({})
});
}
if (this.options.thresholdAutoUpdate && allTestsRun) {
this.updateThresholds({
coverageMap,
thresholds: {
branches: this.options.branches,
functions: this.options.functions,
lines: this.options.lines,
statements: this.options.statements
},
perFile: this.options.perFile,
configurationFile: this.ctx.server.config.configFile
this.checkThresholds({
thresholds: resolvedThresholds,
perFile: this.options.thresholds.perFile
});
if (this.options.thresholds.autoUpdate && allTestsRun) {
if (!this.ctx.server.config.configFile)
throw new Error('Missing configurationFile. The "coverage.thresholds.autoUpdate" can only be enabled when configuration file is used.');
const configFilePath = this.ctx.server.config.configFile;
const configModule = parseModule(await promises.readFile(configFilePath, "utf8"));
this.updateThresholds({
thresholds: resolvedThresholds,
perFile: this.options.thresholds.perFile,
configurationFile: {
write: () => writeFileSync(configFilePath, configModule.generate().code, "utf-8"),
read: () => configModule.exports.default.$type === "function-call" ? configModule.exports.default.$args[0] : configModule.exports.default
}
});
}
this.coverageFiles = /* @__PURE__ */ new Map();
await promises.rm(this.coverageFilesDirectory, { recursive: true });
}
}
async getUntestedFiles(testedFiles, transformResults) {
async getUntestedFiles(testedFiles) {
const transformResults = normalizeTransformResults(this.ctx.vitenode.fetchCache);
const includedFiles = await this.testExclude.glob(this.ctx.config.root);
const uncoveredFiles = includedFiles.map((file) => pathToFileURL(resolve(this.ctx.config.root, file))).filter((file) => !testedFiles.includes(file.href));
return await Promise.all(uncoveredFiles.map(async (uncoveredFile) => {
const { source } = await this.getSources(uncoveredFile.href, transformResults);
return {
url: uncoveredFile.href,
scriptId: "0",
// Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps.
functions: [{
ranges: [{
startOffset: 0,
endOffset: source.length,
count: 0
}],
isBlockCoverage: true,
// This is magical value that indicates an empty report: https://github.com/istanbuljs/v8-to-istanbul/blob/fca5e6a9e6ef38a9cdc3a178d5a6cf9ef82e6cab/lib/v8-to-istanbul.js#LL131C40-L131C40
functionName: "(empty-report)"
}]
};
}));
const uncoveredFiles = includedFiles.map((file) => pathToFileURL(resolve(this.ctx.config.root, file))).filter((file) => !testedFiles.includes(file.pathname));
let merged = { result: [] };
let index = 0;
for (const chunk of toSlices(uncoveredFiles, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length;
debug("Uncovered files %d/%d", index, uncoveredFiles.length);
}
const coverages = await Promise.all(chunk.map(async (filename) => {
const { source } = await this.getSources(filename.href, transformResults);
const coverage = {
url: filename.href,
scriptId: "0",
// Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps.
functions: [{
ranges: [{
startOffset: 0,
endOffset: source.length,
count: 0
}],
isBlockCoverage: true,
// This is magical value that indicates an empty report: https://github.com/istanbuljs/v8-to-istanbul/blob/fca5e6a9e6ef38a9cdc3a178d5a6cf9ef82e6cab/lib/v8-to-istanbul.js#LL131C40-L131C40
functionName: "(empty-report)"
}]
};
return { result: [coverage] };
}));
merged = mergeProcessCovs([merged, ...coverages]);
}
return merged;
}
async getSources(url, transformResults, functions = []) {
var _a;
const filePath = normalize(fileURLToPath(url));
const transformResult = transformResults.get(filePath);
const map = transformResult == null ? void 0 : transformResult.map;
const code = transformResult == null ? void 0 : transformResult.code;
const sourcesContent = ((_a = map == null ? void 0 : map.sourcesContent) == null ? void 0 : _a[0]) || await promises.readFile(filePath, "utf-8").catch(() => {
const map = transformResult?.map;
const code = transformResult?.code;
const sourcesContent = map?.sourcesContent?.[0] || await promises.readFile(filePath, "utf-8").catch(() => {
const length = findLongestFunctionLength(functions);

@@ -335,3 +384,30 @@ return ".".repeat(length);

}
async convertCoverage(coverage, projectName, transformMode) {
const viteNode = this.ctx.projects.find((project) => project.getName() === projectName)?.vitenode || this.ctx.vitenode;
const fetchCache = transformMode ? viteNode.fetchCaches[transformMode] : viteNode.fetchCache;
const transformResults = normalizeTransformResults(fetchCache);
const scriptCoverages = coverage.result.filter((result) => this.testExclude.shouldInstrument(fileURLToPath(result.url)));
const coverageMap = libCoverage.createCoverageMap({});
let index = 0;
for (const chunk of toSlices(scriptCoverages, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length;
debug("Converting %d/%d", index, scriptCoverages.length);
}
await Promise.all(chunk.map(async ({ url, functions }) => {
const sources = await this.getSources(url, transformResults, functions);
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0;
const converter = v8ToIstanbul(url, wrapperLength, sources);
await converter.load();
converter.applyCoverage(functions);
coverageMap.merge(converter.toIstanbul());
}));
}
return coverageMap;
}
}
async function transformCoverage(coverageMap) {
const sourceMapStore = libSourceMaps.createSourceMapStore();
return await sourceMapStore.transformCoverage(coverageMap);
}
function removeViteHelpersFromSourceMaps(source, map) {

@@ -343,3 +419,3 @@ if (!source || !source.match(VITE_EXPORTS_LINE_PATTERN))

const mapWithoutHelpers = sourceWithoutHelpers.generateMap({
hires: true
hires: "boundary"
});

@@ -358,10 +434,8 @@ const combinedMap = remapping(

}
function normalizeTransformResults(fetchCaches) {
function normalizeTransformResults(fetchCache) {
const normalized = /* @__PURE__ */ new Map();
for (const fetchCache of fetchCaches) {
for (const [key, value] of fetchCache.entries()) {
const cleanEntry = cleanUrl(key);
if (!normalized.has(cleanEntry))
normalized.set(cleanEntry, value.result);
}
for (const [key, value] of fetchCache.entries()) {
const cleanEntry = cleanUrl(key);
if (!normalized.has(cleanEntry))
normalized.set(cleanEntry, value.result);
}

@@ -373,3 +447,15 @@ return normalized;

}
function toSlices(array, size) {
return array.reduce((chunks, item) => {
const index = Math.max(0, chunks.length - 1);
const lastChunk = chunks[index] || [];
chunks[index] = lastChunk;
if (lastChunk.length >= size)
chunks.push([item]);
else
lastChunk.push(item);
return chunks;
}, []);
}
export { V8CoverageProvider };
{
"name": "@vitest/coverage-v8",
"type": "module",
"version": "0.34.6",
"version": "1.0.4",
"description": "V8 coverage provider for Vitest",

@@ -29,3 +29,3 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>",

"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"default": "./dist/index.js"
},

@@ -41,3 +41,3 @@ "./*": "./*"

"peerDependencies": {
"vitest": ">=0.32.0 <1"
"vitest": "^1.0.0"
},

@@ -47,20 +47,23 @@ "dependencies": {

"@bcoe/v8-coverage": "^0.2.3",
"istanbul-lib-coverage": "^3.2.0",
"debug": "^4.3.4",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^4.0.1",
"istanbul-reports": "^3.1.5",
"magic-string": "^0.30.1",
"istanbul-reports": "^3.1.6",
"magic-string": "^0.30.5",
"magicast": "^0.3.2",
"picocolors": "^1.0.0",
"std-env": "^3.3.3",
"std-env": "^3.5.0",
"test-exclude": "^6.0.0",
"v8-to-istanbul": "^9.1.0"
"v8-to-istanbul": "^9.2.0"
},
"devDependencies": {
"@types/istanbul-lib-coverage": "^2.0.4",
"@types/istanbul-lib-report": "^3.0.0",
"@types/istanbul-lib-source-maps": "^4.0.1",
"@types/istanbul-reports": "^3.0.1",
"@types/debug": "^4.1.12",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-lib-report": "^3.0.3",
"@types/istanbul-lib-source-maps": "^4.0.4",
"@types/istanbul-reports": "^3.0.4",
"pathe": "^1.1.1",
"vite-node": "0.34.6",
"vitest": "0.34.6"
"vite-node": "1.0.4",
"vitest": "1.0.4"
},

@@ -67,0 +70,0 @@ "scripts": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc