@vitest/coverage-istanbul 0.24.2 → 0.24.4

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.
@@ -0,0 +1,11 @@
1
+ const COVERAGE_STORE_KEY = "__VITEST_COVERAGE__";
2
+
3
+ async function getProvider() {
4
+ const { IstanbulCoverageProvider } = await import('./provider-97efd69a.js');
5
+ return new IstanbulCoverageProvider();
6
+ }
7
+ function takeCoverage() {
8
+ return globalThis[COVERAGE_STORE_KEY];
9
+ }
10
+
11
+ export { COVERAGE_STORE_KEY as C, getProvider as g, takeCoverage as t };
@@ -0,0 +1,234 @@
1
+ import { CoverageProvider, Vitest, ResolvedCoverageOptions, CoverageIstanbulOptions, AfterSuiteRunMeta } from 'vitest';
2
+ import { CoverageMap } from 'istanbul-lib-coverage';
3
+ import { Instrumenter } from 'istanbul-lib-instrument';
4
+
5
+ interface RollupError extends RollupLogProps {
6
+ parserError?: Error;
7
+ stack?: string;
8
+ watchFiles?: string[];
9
+ }
10
+
11
+ interface RollupWarning extends RollupLogProps {
12
+ chunkName?: string;
13
+ cycle?: string[];
14
+ exportName?: string;
15
+ exporter?: string;
16
+ guess?: string;
17
+ importer?: string;
18
+ missing?: string;
19
+ modules?: string[];
20
+ names?: string[];
21
+ reexporter?: string;
22
+ source?: string;
23
+ sources?: string[];
24
+ }
25
+
26
+ interface RollupLogProps {
27
+ code?: string;
28
+ frame?: string;
29
+ hook?: string;
30
+ id?: string;
31
+ loc?: {
32
+ column: number;
33
+ file?: string;
34
+ line: number;
35
+ };
36
+ message: string;
37
+ name?: string;
38
+ plugin?: string;
39
+ pluginCode?: string;
40
+ pos?: number;
41
+ url?: string;
42
+ }
43
+
44
+ interface SourceMap {
45
+ file: string;
46
+ mappings: string;
47
+ names: string[];
48
+ sources: string[];
49
+ sourcesContent: string[];
50
+ version: number;
51
+ toString(): string;
52
+ toUrl(): string;
53
+ }
54
+
55
+ type PartialNull<T> = {
56
+ [P in keyof T]: T[P] | null;
57
+ };
58
+
59
+ interface ModuleOptions {
60
+ meta: CustomPluginOptions;
61
+ moduleSideEffects: boolean | 'no-treeshake';
62
+ syntheticNamedExports: boolean | string;
63
+ }
64
+
65
+ interface PluginCache {
66
+ delete(id: string): boolean;
67
+ get<T = any>(id: string): T;
68
+ has(id: string): boolean;
69
+ set<T = any>(id: string, value: T): void;
70
+ }
71
+
72
+ interface MinimalPluginContext {
73
+ meta: PluginContextMeta;
74
+ }
75
+
76
+ interface EmittedAsset {
77
+ fileName?: string;
78
+ name?: string;
79
+ source?: string | Uint8Array;
80
+ type: 'asset';
81
+ }
82
+
83
+ interface EmittedChunk {
84
+ fileName?: string;
85
+ id: string;
86
+ implicitlyLoadedAfterOneOf?: string[];
87
+ importer?: string;
88
+ name?: string;
89
+ preserveSignature?: PreserveEntrySignaturesOption;
90
+ type: 'chunk';
91
+ }
92
+
93
+ type EmittedFile = EmittedAsset | EmittedChunk;
94
+
95
+ type EmitAsset = (name: string, source?: string | Uint8Array) => string;
96
+
97
+ type EmitChunk = (id: string, options?: { name?: string }) => string;
98
+
99
+ type EmitFile = (emittedFile: EmittedFile) => string;
100
+
101
+ interface ModuleInfo extends ModuleOptions {
102
+ ast: AcornNode | null;
103
+ code: string | null;
104
+ dynamicImporters: readonly string[];
105
+ dynamicallyImportedIdResolutions: readonly ResolvedId[];
106
+ dynamicallyImportedIds: readonly string[];
107
+ hasDefaultExport: boolean | null;
108
+ /** @deprecated Use `moduleSideEffects` instead */
109
+ hasModuleSideEffects: boolean | 'no-treeshake';
110
+ id: string;
111
+ implicitlyLoadedAfterOneOf: readonly string[];
112
+ implicitlyLoadedBefore: readonly string[];
113
+ importedIdResolutions: readonly ResolvedId[];
114
+ importedIds: readonly string[];
115
+ importers: readonly string[];
116
+ isEntry: boolean;
117
+ isExternal: boolean;
118
+ isIncluded: boolean | null;
119
+ }
120
+
121
+ type GetModuleInfo = (moduleId: string) => ModuleInfo | null;
122
+
123
+ interface CustomPluginOptions {
124
+ [plugin: string]: any;
125
+ }
126
+
127
+ interface PluginContext extends MinimalPluginContext {
128
+ addWatchFile: (id: string) => void;
129
+ cache: PluginCache;
130
+ /** @deprecated Use `this.emitFile` instead */
131
+ emitAsset: EmitAsset;
132
+ /** @deprecated Use `this.emitFile` instead */
133
+ emitChunk: EmitChunk;
134
+ emitFile: EmitFile;
135
+ error: (err: RollupError | string, pos?: number | { column: number; line: number }) => never;
136
+ /** @deprecated Use `this.getFileName` instead */
137
+ getAssetFileName: (assetReferenceId: string) => string;
138
+ /** @deprecated Use `this.getFileName` instead */
139
+ getChunkFileName: (chunkReferenceId: string) => string;
140
+ getFileName: (fileReferenceId: string) => string;
141
+ getModuleIds: () => IterableIterator<string>;
142
+ getModuleInfo: GetModuleInfo;
143
+ getWatchFiles: () => string[];
144
+ /** @deprecated Use `this.resolve` instead */
145
+ isExternal: IsExternal;
146
+ load: (
147
+ options: { id: string; resolveDependencies?: boolean } & Partial<PartialNull<ModuleOptions>>
148
+ ) => Promise<ModuleInfo>;
149
+ /** @deprecated Use `this.getModuleIds` instead */
150
+ moduleIds: IterableIterator<string>;
151
+ parse: (input: string, options?: any) => AcornNode;
152
+ resolve: (
153
+ source: string,
154
+ importer?: string,
155
+ options?: { custom?: CustomPluginOptions; isEntry?: boolean; skipSelf?: boolean }
156
+ ) => Promise<ResolvedId | null>;
157
+ /** @deprecated Use `this.resolve` instead */
158
+ resolveId: (source: string, importer?: string) => Promise<string | null>;
159
+ setAssetSource: (assetReferenceId: string, source: string | Uint8Array) => void;
160
+ warn: (warning: RollupWarning | string, pos?: number | { column: number; line: number }) => void;
161
+ }
162
+
163
+ interface PluginContextMeta {
164
+ rollupVersion: string;
165
+ watchMode: boolean;
166
+ }
167
+
168
+ interface ResolvedId extends ModuleOptions {
169
+ external: boolean | 'absolute';
170
+ id: string;
171
+ }
172
+
173
+ type IsExternal = (
174
+ source: string,
175
+ importer: string | undefined,
176
+ isResolved: boolean
177
+ ) => boolean;
178
+
179
+ interface TransformPluginContext extends PluginContext {
180
+ getCombinedSourcemap: () => SourceMap;
181
+ }
182
+ type PreserveEntrySignaturesOption = false | 'strict' | 'allow-extension' | 'exports-only';
183
+
184
+ interface AcornNode {
185
+ end: number;
186
+ start: number;
187
+ type: string;
188
+ }
189
+
190
+ declare type Threshold = 'lines' | 'functions' | 'statements' | 'branches';
191
+ interface TestExclude {
192
+ new (opts: {
193
+ cwd?: string | string[];
194
+ include?: string | string[];
195
+ exclude?: string | string[];
196
+ extension?: string | string[];
197
+ excludeNodeModules?: boolean;
198
+ }): {
199
+ shouldInstrument(filePath: string): boolean;
200
+ glob(cwd: string): Promise<string[]>;
201
+ };
202
+ }
203
+ declare class IstanbulCoverageProvider implements CoverageProvider {
204
+ name: string;
205
+ ctx: Vitest;
206
+ options: ResolvedCoverageOptions & CoverageIstanbulOptions & {
207
+ provider: 'istanbul';
208
+ };
209
+ instrumenter: Instrumenter;
210
+ testExclude: InstanceType<TestExclude>;
211
+ /**
212
+ * Coverage objects collected from workers.
213
+ * Some istanbul utilizers write these into file system instead of storing in memory.
214
+ * If storing in memory causes issues, we can simply write these into fs in `onAfterSuiteRun`
215
+ * and read them back when merging coverage objects in `onAfterAllFilesRun`.
216
+ */
217
+ coverages: any[];
218
+ initialize(ctx: Vitest): void;
219
+ resolveOptions(): ResolvedCoverageOptions;
220
+ onFileTransform(sourceCode: string, id: string, pluginCtx: TransformPluginContext): {
221
+ code: string;
222
+ map: any;
223
+ } | undefined;
224
+ onAfterSuiteRun({ coverage }: AfterSuiteRunMeta): void;
225
+ clean(clean?: boolean): Promise<void>;
226
+ reportCoverage(): Promise<void>;
227
+ checkThresholds(coverageMap: CoverageMap, thresholds: Record<Threshold, number | undefined>): void;
228
+ includeUntestedFiles(coverageMap: CoverageMap): Promise<void>;
229
+ }
230
+
231
+ declare function getProvider(): Promise<IstanbulCoverageProvider>;
232
+ declare function takeCoverage(): any;
233
+
234
+ export { getProvider, takeCoverage };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { g as getProvider, t as takeCoverage } from './index-cc7ec1dd.js';
@@ -0,0 +1,337 @@
1
+ import { existsSync, promises } from 'fs';
2
+ import path from 'path';
3
+ import { defaultExclude, defaultInclude, configDefaults } from 'vitest/config';
4
+ import libReport from 'istanbul-lib-report';
5
+ import reports from 'istanbul-reports';
6
+ import libCoverage from 'istanbul-lib-coverage';
7
+ import libSourceMaps from 'istanbul-lib-source-maps';
8
+ import { createInstrumenter } from 'istanbul-lib-instrument';
9
+ import _TestExclude from 'test-exclude';
10
+ import { C as COVERAGE_STORE_KEY } from './index-cc7ec1dd.js';
11
+
12
+ function normalizeWindowsPath(input = "") {
13
+ if (!input.includes("\\")) {
14
+ return input;
15
+ }
16
+ return input.replace(/\\/g, "/");
17
+ }
18
+
19
+ const _UNC_REGEX = /^[/][/]/;
20
+ const _UNC_DRIVE_REGEX = /^[/][/]([.]{1,2}[/])?([a-zA-Z]):[/]/;
21
+ const _IS_ABSOLUTE_RE = /^\/|^\\|^[a-zA-Z]:[/\\]/;
22
+ const sep = "/";
23
+ const delimiter = ":";
24
+ const normalize = function(path2) {
25
+ if (path2.length === 0) {
26
+ return ".";
27
+ }
28
+ path2 = normalizeWindowsPath(path2);
29
+ const isUNCPath = path2.match(_UNC_REGEX);
30
+ const hasUNCDrive = isUNCPath && path2.match(_UNC_DRIVE_REGEX);
31
+ const isPathAbsolute = isAbsolute(path2);
32
+ const trailingSeparator = path2[path2.length - 1] === "/";
33
+ path2 = normalizeString(path2, !isPathAbsolute);
34
+ if (path2.length === 0) {
35
+ if (isPathAbsolute) {
36
+ return "/";
37
+ }
38
+ return trailingSeparator ? "./" : ".";
39
+ }
40
+ if (trailingSeparator) {
41
+ path2 += "/";
42
+ }
43
+ if (isUNCPath) {
44
+ if (hasUNCDrive) {
45
+ return `//./${path2}`;
46
+ }
47
+ return `//${path2}`;
48
+ }
49
+ return isPathAbsolute && !isAbsolute(path2) ? `/${path2}` : path2;
50
+ };
51
+ const join = function(...args) {
52
+ if (args.length === 0) {
53
+ return ".";
54
+ }
55
+ let joined;
56
+ for (let i = 0; i < args.length; ++i) {
57
+ const arg = args[i];
58
+ if (arg.length > 0) {
59
+ if (joined === void 0) {
60
+ joined = arg;
61
+ } else {
62
+ joined += `/${arg}`;
63
+ }
64
+ }
65
+ }
66
+ if (joined === void 0) {
67
+ return ".";
68
+ }
69
+ return normalize(joined);
70
+ };
71
+ const resolve = function(...args) {
72
+ args = args.map((arg) => normalizeWindowsPath(arg));
73
+ let resolvedPath = "";
74
+ let resolvedAbsolute = false;
75
+ for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
76
+ const path2 = i >= 0 ? args[i] : process.cwd();
77
+ if (path2.length === 0) {
78
+ continue;
79
+ }
80
+ resolvedPath = `${path2}/${resolvedPath}`;
81
+ resolvedAbsolute = isAbsolute(path2);
82
+ }
83
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
84
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
85
+ return `/${resolvedPath}`;
86
+ }
87
+ return resolvedPath.length > 0 ? resolvedPath : ".";
88
+ };
89
+ function normalizeString(path2, allowAboveRoot) {
90
+ let res = "";
91
+ let lastSegmentLength = 0;
92
+ let lastSlash = -1;
93
+ let dots = 0;
94
+ let char = null;
95
+ for (let i = 0; i <= path2.length; ++i) {
96
+ if (i < path2.length) {
97
+ char = path2[i];
98
+ } else if (char === "/") {
99
+ break;
100
+ } else {
101
+ char = "/";
102
+ }
103
+ if (char === "/") {
104
+ if (lastSlash === i - 1 || dots === 1) ; else if (dots === 2) {
105
+ if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
106
+ if (res.length > 2) {
107
+ const lastSlashIndex = res.lastIndexOf("/");
108
+ if (lastSlashIndex === -1) {
109
+ res = "";
110
+ lastSegmentLength = 0;
111
+ } else {
112
+ res = res.slice(0, lastSlashIndex);
113
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
114
+ }
115
+ lastSlash = i;
116
+ dots = 0;
117
+ continue;
118
+ } else if (res.length !== 0) {
119
+ res = "";
120
+ lastSegmentLength = 0;
121
+ lastSlash = i;
122
+ dots = 0;
123
+ continue;
124
+ }
125
+ }
126
+ if (allowAboveRoot) {
127
+ res += res.length > 0 ? "/.." : "..";
128
+ lastSegmentLength = 2;
129
+ }
130
+ } else {
131
+ if (res.length > 0) {
132
+ res += `/${path2.slice(lastSlash + 1, i)}`;
133
+ } else {
134
+ res = path2.slice(lastSlash + 1, i);
135
+ }
136
+ lastSegmentLength = i - lastSlash - 1;
137
+ }
138
+ lastSlash = i;
139
+ dots = 0;
140
+ } else if (char === "." && dots !== -1) {
141
+ ++dots;
142
+ } else {
143
+ dots = -1;
144
+ }
145
+ }
146
+ return res;
147
+ }
148
+ const isAbsolute = function(p) {
149
+ return _IS_ABSOLUTE_RE.test(p);
150
+ };
151
+ const toNamespacedPath = function(p) {
152
+ return normalizeWindowsPath(p);
153
+ };
154
+ const extname = function(p) {
155
+ return path.posix.extname(normalizeWindowsPath(p));
156
+ };
157
+ const relative = function(from, to) {
158
+ return path.posix.relative(normalizeWindowsPath(from), normalizeWindowsPath(to));
159
+ };
160
+ const dirname = function(p) {
161
+ return path.posix.dirname(normalizeWindowsPath(p));
162
+ };
163
+ const format = function(p) {
164
+ return normalizeWindowsPath(path.posix.format(p));
165
+ };
166
+ const basename = function(p, ext) {
167
+ return path.posix.basename(normalizeWindowsPath(p), ext);
168
+ };
169
+ const parse = function(p) {
170
+ return path.posix.parse(normalizeWindowsPath(p));
171
+ };
172
+
173
+ const _path = /*#__PURE__*/Object.freeze({
174
+ __proto__: null,
175
+ sep: sep,
176
+ delimiter: delimiter,
177
+ normalize: normalize,
178
+ join: join,
179
+ resolve: resolve,
180
+ normalizeString: normalizeString,
181
+ isAbsolute: isAbsolute,
182
+ toNamespacedPath: toNamespacedPath,
183
+ extname: extname,
184
+ relative: relative,
185
+ dirname: dirname,
186
+ format: format,
187
+ basename: basename,
188
+ parse: parse
189
+ });
190
+
191
+ ({
192
+ ..._path
193
+ });
194
+
195
+ class IstanbulCoverageProvider {
196
+ constructor() {
197
+ this.name = "istanbul";
198
+ this.coverages = [];
199
+ }
200
+ initialize(ctx) {
201
+ this.ctx = ctx;
202
+ this.options = resolveIstanbulOptions(ctx.config.coverage, ctx.config.root);
203
+ this.instrumenter = createInstrumenter({
204
+ produceSourceMap: true,
205
+ autoWrap: false,
206
+ esModules: true,
207
+ compact: false,
208
+ coverageVariable: COVERAGE_STORE_KEY,
209
+ coverageGlobalScope: "globalThis",
210
+ coverageGlobalScopeFunc: false,
211
+ ignoreClassMethods: this.options.ignoreClassMethods
212
+ });
213
+ this.testExclude = new _TestExclude({
214
+ cwd: ctx.config.root,
215
+ include: typeof this.options.include === "undefined" ? void 0 : [...this.options.include],
216
+ exclude: [...defaultExclude, ...defaultInclude, ...this.options.exclude],
217
+ excludeNodeModules: true,
218
+ extension: configDefaults.coverage.extension
219
+ });
220
+ }
221
+ resolveOptions() {
222
+ return this.options;
223
+ }
224
+ onFileTransform(sourceCode, id, pluginCtx) {
225
+ if (!this.testExclude.shouldInstrument(id))
226
+ return;
227
+ const sourceMap = pluginCtx.getCombinedSourcemap();
228
+ sourceMap.sources = sourceMap.sources.map(removeQueryParameters);
229
+ const code = this.instrumenter.instrumentSync(sourceCode, id, sourceMap);
230
+ const map = this.instrumenter.lastSourceMap();
231
+ return { code, map };
232
+ }
233
+ onAfterSuiteRun({ coverage }) {
234
+ this.coverages.push(coverage);
235
+ }
236
+ async clean(clean = true) {
237
+ if (clean && existsSync(this.options.reportsDirectory))
238
+ await promises.rm(this.options.reportsDirectory, { recursive: true, force: true });
239
+ this.coverages = [];
240
+ }
241
+ async reportCoverage() {
242
+ const mergedCoverage = this.coverages.reduce((coverage, previousCoverageMap) => {
243
+ const map = libCoverage.createCoverageMap(coverage);
244
+ map.merge(previousCoverageMap);
245
+ return map;
246
+ }, {});
247
+ if (this.options.all)
248
+ await this.includeUntestedFiles(mergedCoverage);
249
+ const sourceMapStore = libSourceMaps.createSourceMapStore();
250
+ const coverageMap = await sourceMapStore.transformCoverage(mergedCoverage);
251
+ const context = libReport.createContext({
252
+ dir: this.options.reportsDirectory,
253
+ coverageMap,
254
+ sourceFinder: sourceMapStore.sourceFinder,
255
+ watermarks: this.options.watermarks
256
+ });
257
+ for (const reporter of this.options.reporter) {
258
+ reports.create(reporter, {
259
+ skipFull: this.options.skipFull,
260
+ projectRoot: this.ctx.config.root
261
+ }).execute(context);
262
+ }
263
+ if (this.options.branches || this.options.functions || this.options.lines || this.options.statements) {
264
+ this.checkThresholds(coverageMap, {
265
+ branches: this.options.branches,
266
+ functions: this.options.functions,
267
+ lines: this.options.lines,
268
+ statements: this.options.statements
269
+ });
270
+ }
271
+ }
272
+ checkThresholds(coverageMap, thresholds) {
273
+ const summaries = this.options.perFile ? coverageMap.files().map((file) => ({
274
+ file,
275
+ summary: coverageMap.fileCoverageFor(file).toSummary()
276
+ })) : [{
277
+ file: null,
278
+ summary: coverageMap.getCoverageSummary()
279
+ }];
280
+ for (const { summary, file } of summaries) {
281
+ for (const thresholdKey of ["lines", "functions", "statements", "branches"]) {
282
+ const threshold = thresholds[thresholdKey];
283
+ if (threshold !== void 0) {
284
+ const coverage = summary.data[thresholdKey].pct;
285
+ if (coverage < threshold) {
286
+ process.exitCode = 1;
287
+ let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet`;
288
+ if (!this.options.perFile)
289
+ errorMessage += " global";
290
+ errorMessage += ` threshold (${threshold}%)`;
291
+ if (this.options.perFile && file)
292
+ errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
293
+ console.error(errorMessage);
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ async includeUntestedFiles(coverageMap) {
300
+ const includedFiles = await this.testExclude.glob(this.ctx.config.root);
301
+ const uncoveredFiles = includedFiles.map((file) => resolve(this.ctx.config.root, file)).filter((file) => !coverageMap.data[file]);
302
+ const transformResults = await Promise.all(uncoveredFiles.map(async (filename) => {
303
+ const transformResult = await this.ctx.vitenode.transformRequest(filename);
304
+ return { transformResult, filename };
305
+ }));
306
+ for (const { transformResult, filename } of transformResults) {
307
+ const sourceMap = transformResult == null ? void 0 : transformResult.map;
308
+ if (sourceMap) {
309
+ this.instrumenter.instrumentSync(
310
+ transformResult.code,
311
+ filename,
312
+ sourceMap
313
+ );
314
+ const lastCoverage = this.instrumenter.lastFileCoverage();
315
+ if (lastCoverage)
316
+ coverageMap.addFileCoverage(lastCoverage);
317
+ }
318
+ }
319
+ }
320
+ }
321
+ function resolveIstanbulOptions(options, root) {
322
+ const reportsDirectory = resolve(root, options.reportsDirectory || configDefaults.coverage.reportsDirectory);
323
+ const resolved = {
324
+ ...configDefaults.coverage,
325
+ ...options,
326
+ provider: "istanbul",
327
+ reportsDirectory,
328
+ tempDirectory: resolve(reportsDirectory, "tmp"),
329
+ reporter: Array.isArray(options.reporter) ? options.reporter : [options.reporter]
330
+ };
331
+ return resolved;
332
+ }
333
+ function removeQueryParameters(filename) {
334
+ return filename.split("?")[0];
335
+ }
336
+
337
+ export { IstanbulCoverageProvider };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/coverage-istanbul",
3
3
  "type": "module",
4
- "version": "0.24.2",
4
+ "version": "0.24.4",
5
5
  "description": "Istanbul coverage provider for Vitest",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "istanbul-lib-source-maps": "^4.0.1",
44
44
  "istanbul-reports": "^3.1.5",
45
45
  "test-exclude": "^6.0.0",
46
- "vitest": "0.24.2"
46
+ "vitest": "0.24.4"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/istanbul-lib-coverage": "^2.0.4",