Skip to content

Commit 0fe631e

Browse files
authored
perf(coverage-v8): skip unnecessary coverage conversion (#1298)
1 parent 024e550 commit 0fe631e

6 files changed

Lines changed: 357 additions & 17 deletions

File tree

knip.jsonc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,15 @@
9494
"tests/**/*.ts",
9595
],
9696
},
97+
"packages/coverage-v8": {
98+
"entry": [
99+
// Test files
100+
"tests/**/*.ts",
101+
],
102+
},
97103
// No explicit entry needed for the following packages — knip auto-infers from package.json exports:
98-
// packages/browser-react, packages/adapter-rslib,
99-
// packages/adapter-rsbuild, packages/adapter-rspack
104+
// packages/browser-react, packages/adapter-rslib, packages/adapter-rsbuild,
105+
// packages/adapter-rspack
100106

101107
"packages/vscode": {
102108
"entry": [

packages/coverage-v8/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
],
1616
"scripts": {
1717
"build": "rslib build",
18+
"test": "rstest",
1819
"typecheck": "tsc --noEmit"
1920
},
2021
"dependencies": {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { defineConfig } from '@rstest/core';
2+
import { withRslibConfig } from '../adapter-rslib/src';
3+
4+
export default defineConfig({
5+
extends: withRslibConfig({
6+
cwd: __dirname,
7+
}),
8+
name: 'coverage-v8',
9+
setupFiles: ['../../scripts/rstest.setup.ts'],
10+
include: ['<rootDir>/tests/**/*.test.ts'],
11+
globals: true,
12+
source: {
13+
tsconfigPath: './tests/tsconfig.json',
14+
},
15+
tools: {
16+
rspack: {
17+
watchOptions: {
18+
ignored: /test-temp-.*/,
19+
},
20+
},
21+
},
22+
});

packages/coverage-v8/src/provider.ts

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export class CoverageProvider implements RstestCoverageProvider {
3333
private isMatch: (filePath: string) => boolean;
3434
private isIncluded: (filePath: string) => boolean;
3535
private isExcluded: (filePath: string) => boolean;
36+
private dictLookupCache = new WeakMap<
37+
Record<string, string>,
38+
Map<string, string>
39+
>();
3640

3741
constructor(
3842
public options: NormalizedCoverageOptions,
@@ -94,21 +98,33 @@ export class CoverageProvider implements RstestCoverageProvider {
9498
if (!dict) return undefined;
9599
if (dict[filePath]) return dict[filePath];
96100

97-
for (const [key, value] of Object.entries(dict)) {
98-
const normalizedKey = key.replace(/\\/g, '/');
99-
if (normalizedKey === filePath) return value;
100-
if (
101-
filePath.startsWith('/private/') &&
102-
normalizedKey === filePath.slice('/private'.length)
103-
) {
104-
return value;
105-
}
106-
if (normalizedKey.toLowerCase() === filePath.toLowerCase()) {
107-
return value;
101+
let lookup = this.dictLookupCache.get(dict);
102+
if (!lookup) {
103+
lookup = new Map();
104+
for (const [key, value] of Object.entries(dict)) {
105+
const normalizedKey = key.replace(/\\/g, '/');
106+
if (!lookup.has(normalizedKey)) {
107+
lookup.set(normalizedKey, value);
108+
}
109+
110+
const lowerKey = normalizedKey.toLowerCase();
111+
if (!lookup.has(lowerKey)) {
112+
lookup.set(lowerKey, value);
113+
}
108114
}
115+
this.dictLookupCache.set(dict, lookup);
116+
}
117+
118+
const normalizedPath = filePath.replace(/\\/g, '/');
119+
const directMatch = lookup.get(normalizedPath);
120+
if (directMatch) return directMatch;
121+
122+
if (filePath.startsWith('/private/')) {
123+
const privateMatch = lookup.get(filePath.slice('/private'.length));
124+
if (privateMatch) return privateMatch;
109125
}
110126

111-
return undefined;
127+
return lookup.get(normalizedPath.toLowerCase());
112128
}
113129

114130
private isNodeModulesPath(filePath: string): boolean {
@@ -177,6 +193,20 @@ export class CoverageProvider implements RstestCoverageProvider {
177193
);
178194
}
179195

196+
private hasInlineSourceMap(code: string): boolean {
197+
return /[#@]\s*sourceMappingURL\s*=\s*data:application\/json(?:;[^'",\s]*)*,/i.test(
198+
code,
199+
);
200+
}
201+
202+
private async hasInlineSourceMapOnDisk(filePath: string): Promise<boolean> {
203+
try {
204+
return this.hasInlineSourceMap(await fs.readFile(filePath, 'utf-8'));
205+
} catch (_err) {
206+
return false;
207+
}
208+
}
209+
180210
private async getTransformedSource(
181211
filePath: string,
182212
options?: {
@@ -312,11 +342,27 @@ export class CoverageProvider implements RstestCoverageProvider {
312342

313343
if (!this.isMatch(filePath)) return;
314344

315-
const hasSourceMap = Boolean(
316-
this.findInDict(options?.sourceMaps, filePath),
345+
const sourceMapStr = this.findInDict(options?.sourceMaps, filePath);
346+
const assetSource = this.findInDict(options?.assetFiles, filePath);
347+
let hasSourceMap = Boolean(
348+
sourceMapStr || (assetSource && this.hasInlineSourceMap(assetSource)),
317349
);
318350

319-
if (!this.shouldProcessEntry(filePath) && !hasSourceMap) return;
351+
if (!this.shouldProcessEntry(filePath) && !hasSourceMap) {
352+
hasSourceMap = await this.hasInlineSourceMapOnDisk(filePath);
353+
if (!hasSourceMap) return;
354+
}
355+
356+
if (!hasSourceMap) {
357+
const originalTestPath = this.toProjectRelativePath(filePath);
358+
if (
359+
this.isExcluded(originalTestPath) ||
360+
!this.isIncluded(originalTestPath)
361+
) {
362+
hasSourceMap = await this.hasInlineSourceMapOnDisk(filePath);
363+
if (!hasSourceMap) return;
364+
}
365+
}
320366

321367
try {
322368
const istanbulData = await this.convertWithAst(

0 commit comments

Comments
 (0)