Skip to content

Commit ffb753f

Browse files
authored
perf: do prettification after deduping (#18)
refs #17 Local run of 213 files (Wallace's Playwright coverage report) went from 1.32s to 0.24s.
1 parent 920d79b commit ffb753f

File tree

230 files changed

+2805
-438
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

230 files changed

+2805
-438
lines changed

src/cli/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22

3-
import { validate_arguments, parse_arguments, InvalidArgumentsError } from './arguments.js'
4-
import { program, MissingDataError } from './program.js'
3+
import { validate_arguments, parse_arguments } from './arguments.js'
4+
import { program } from './program.js'
55
import { read } from './file-reader.js'
66
import { print as pretty } from './reporters/pretty.js'
77
import { print as tap } from './reporters/tap.js'

src/cli/reporters/pretty.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,20 @@ export function print({ report, context }: Report, params: CliArguments) {
6464
console.log(styleText('dim', '─'.repeat(terminal_width)))
6565

6666
let lines = sheet.text.split('\n')
67-
let line_coverage = sheet.line_coverage
6867

69-
for (let i = 0; i < lines.length; i++) {
70-
if (line_coverage[i] === 1) continue
71-
72-
// Rewind cursor N lines to render N previous lines
73-
for (let j = i - NUM_LEADING_LINES; j < i; j++) {
74-
// Make sure that we don't try to start before line 0
75-
if (j >= 0) {
76-
console.log(styleText('dim', line_number(j)), styleText('dim', indent(lines[j])))
77-
}
68+
for (let chunk of sheet.chunks.filter((chunk) => !chunk.is_covered)) {
69+
// Render N leading lines
70+
for (let x = Math.max(chunk.start_line - NUM_LEADING_LINES, 0); x < chunk.start_line; x++) {
71+
console.log(styleText('dim', line_number(x)), styleText('dim', indent(lines[x - 1])))
7872
}
79-
80-
// Render uncovered lines while increasing cursor until reaching next covered block
81-
while (line_coverage[i] === 0) {
82-
console.log(styleText('red', line_number(i, false)), indent(lines[i]))
83-
i++
73+
// Render the uncovered chunk
74+
for (let i = chunk.start_line; i <= chunk.end_line; i++) {
75+
console.log(styleText('red', line_number(i, false)), indent(lines[i - 1]))
8476
}
85-
86-
// Forward cursor N lines to render N trailing lines
87-
for (let end = i + NUM_TRAILING_LINES; i < end && i < lines.length; i++) {
88-
console.log(styleText('dim', line_number(i)), styleText('dim', indent(lines[i])))
77+
// Render N trailing lines
78+
for (let y = chunk.end_line; y < Math.min(chunk.end_line + NUM_TRAILING_LINES, lines.length); y++) {
79+
console.log(styleText('dim', line_number(y)), styleText('dim', indent(lines[y - 1])))
8980
}
90-
9181
// Show empty line between blocks
9282
console.log()
9383
}

src/lib/chunkify.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { test, expect } from '@playwright/test'
2+
import { chunkify, type ChunkedCoverage } from './chunkify'
3+
4+
test('creates chunks with outer chunks covered', () => {
5+
let coverage = {
6+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
7+
ranges: [
8+
{ start: 0, end: 17 },
9+
{ start: 38, end: 56 },
10+
],
11+
url: 'https://example.com',
12+
}
13+
let result = chunkify(coverage)
14+
delete coverage.ranges
15+
expect(result).toEqual({
16+
...coverage,
17+
chunks: [
18+
{
19+
start_offset: 0,
20+
end_offset: 17,
21+
is_covered: true,
22+
},
23+
{
24+
start_offset: 17,
25+
end_offset: 38,
26+
is_covered: false,
27+
},
28+
{
29+
start_offset: 38,
30+
end_offset: 56,
31+
is_covered: true,
32+
},
33+
],
34+
} satisfies ChunkedCoverage)
35+
})
36+
37+
test('creates chunks with only middle chunk covered', () => {
38+
let coverage = {
39+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
40+
ranges: [{ start: 17, end: 38 }],
41+
url: 'https://example.com',
42+
}
43+
let result = chunkify(coverage)
44+
delete coverage.ranges
45+
expect(result).toEqual({
46+
...coverage,
47+
chunks: [
48+
{
49+
start_offset: 0,
50+
end_offset: 17,
51+
is_covered: false,
52+
},
53+
{
54+
start_offset: 17,
55+
end_offset: 38,
56+
is_covered: true,
57+
},
58+
{
59+
start_offset: 38,
60+
end_offset: 56,
61+
is_covered: false,
62+
},
63+
],
64+
} satisfies ChunkedCoverage)
65+
})
66+
67+
test('creates a single chunk when all is covered', () => {
68+
let coverage = {
69+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
70+
ranges: [{ start: 0, end: 56 }],
71+
url: 'https://example.com',
72+
}
73+
let result = chunkify(coverage)
74+
delete coverage.ranges
75+
expect(result).toEqual({
76+
...coverage,
77+
chunks: [
78+
{
79+
start_offset: 0,
80+
end_offset: 56,
81+
is_covered: true,
82+
},
83+
],
84+
} satisfies ChunkedCoverage)
85+
})
86+
87+
test('creates a single chunk when none is covered', () => {
88+
let coverage = {
89+
text: 'a { color: red; } b { color: green; } c { color: blue; }',
90+
ranges: [],
91+
url: 'https://example.com',
92+
}
93+
let result = chunkify(coverage)
94+
delete coverage.ranges
95+
expect(result).toEqual({
96+
...coverage,
97+
chunks: [
98+
{
99+
start_offset: 0,
100+
end_offset: 56,
101+
is_covered: false,
102+
},
103+
],
104+
} satisfies ChunkedCoverage)
105+
})

src/lib/chunkify.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { Coverage } from './parse-coverage'
2+
3+
type Chunk = {
4+
start_offset: number
5+
end_offset: number
6+
is_covered: boolean
7+
}
8+
9+
export type ChunkedCoverage = Omit<Coverage, 'ranges'> & {
10+
chunks: Chunk[]
11+
}
12+
13+
function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
14+
let new_chunks: Chunk[] = []
15+
let previous_chunk: Chunk | undefined
16+
17+
for (let i = 0; i < stylesheet.chunks.length; i++) {
18+
let chunk = stylesheet.chunks.at(i)!
19+
20+
// If the current chunk is only whitespace or empty, ignore it
21+
if (/^\s+$/.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset))) {
22+
continue
23+
}
24+
25+
let latest_chunk = new_chunks.at(-1)
26+
27+
// merge current and previous if they are both covered or uncovered
28+
if (i > 0 && previous_chunk && latest_chunk) {
29+
if (previous_chunk.is_covered === chunk.is_covered) {
30+
latest_chunk.end_offset = chunk.end_offset
31+
previous_chunk = chunk
32+
continue
33+
}
34+
// If the current chunk is only whitespace or empty, add it to the previous
35+
else if (/^\s+$/.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset)) || chunk.end_offset === chunk.start_offset) {
36+
latest_chunk.end_offset = chunk.end_offset
37+
// do not update previous_chunk
38+
continue
39+
}
40+
}
41+
42+
previous_chunk = chunk
43+
new_chunks.push(chunk)
44+
}
45+
46+
return {
47+
...stylesheet,
48+
chunks: new_chunks,
49+
}
50+
}
51+
52+
export function chunkify(stylesheet: Coverage): ChunkedCoverage {
53+
let chunks = []
54+
let offset = 0
55+
56+
for (let range of stylesheet.ranges) {
57+
// Create non-covered chunk
58+
if (offset !== range.start) {
59+
chunks.push({
60+
start_offset: offset,
61+
end_offset: range.start,
62+
is_covered: false,
63+
})
64+
offset = range.start
65+
}
66+
67+
chunks.push({
68+
start_offset: range.start,
69+
end_offset: range.end,
70+
is_covered: true,
71+
})
72+
offset = range.end
73+
}
74+
75+
// fill up last chunk if necessary:
76+
if (offset !== stylesheet.text.length - 1) {
77+
chunks.push({
78+
start_offset: offset,
79+
end_offset: stylesheet.text.length,
80+
is_covered: false,
81+
})
82+
}
83+
84+
let merged = merge({
85+
url: stylesheet.url,
86+
text: stylesheet.text,
87+
chunks,
88+
})
89+
90+
return merged
91+
}

src/lib/css-tree.d.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/lib/decuplicate.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,61 @@
11
import type { Coverage, Range } from './parse-coverage.js'
2+
3+
// Combine multiple adjecent ranges into a single one
4+
export function concatenate(ranges: Set<Range> | Range[]): Range[] {
5+
let result: Range[] = []
6+
7+
for (let range of ranges) {
8+
// Update the last range if this range starts at last-range-end + 1
9+
if (result.length > 0 && (result.at(-1)!.end === range.start - 1 || result.at(-1)!.end === range.start)) {
10+
result.at(-1)!.end = range.end
11+
} else {
12+
result.push(range)
13+
}
14+
}
15+
16+
return result
17+
}
18+
19+
function dedupe_list(ranges: Range[]): Set<Range> {
20+
let new_ranges: Set<Range> = new Set()
21+
22+
outer: for (let range of ranges) {
23+
for (let processed_range of new_ranges) {
24+
// Case: an existing range fits within this range -> replace it
25+
// { start: 0, end: 100 },
26+
// { start: 0, end: 200 }
27+
if (range.start <= processed_range.start && range.end >= processed_range.end) {
28+
new_ranges.delete(processed_range)
29+
new_ranges.add(range)
30+
continue outer
31+
}
32+
33+
// Case: this range fits within an existing range -> skip it
34+
// { start: 324, end: 485 }, --> exists
35+
// { start: 364, end: 485 }, --> skip
36+
// { start: 404, end: 485 }, --> skip
37+
if (range.start >= processed_range.start && range.end <= processed_range.end) {
38+
// console.log('skip', range)
39+
continue outer
40+
}
41+
// Case: ranges partially overlap
42+
// { start: 324, end: 444 },
43+
// { start: 364, end: 485 },
44+
if (range.start < processed_range.end && range.start > processed_range.start && range.end > processed_range.end) {
45+
new_ranges.delete(processed_range)
46+
new_ranges.add({
47+
start: processed_range.start,
48+
end: range.end,
49+
})
50+
continue outer
51+
}
52+
}
53+
new_ranges.add(range)
54+
}
55+
56+
return new_ranges
57+
}
58+
259
/**
360
* @description
461
* prerequisites
@@ -18,12 +75,15 @@ export function deduplicate_entries(entries: Coverage[]): Coverage[] {
1875
// If not, add them
1976
for (let range of entry.ranges) {
2077
let found = false
78+
2179
for (let checked_range of ranges) {
80+
// find exact range
2281
if (checked_range.start === range.start && checked_range.end === range.end) {
2382
found = true
2483
break
2584
}
2685
}
86+
2787
if (!found) {
2888
ranges.push(range)
2989
}
@@ -36,5 +96,9 @@ export function deduplicate_entries(entries: Coverage[]): Coverage[] {
3696
}
3797
}
3898

39-
return Array.from(checked_stylesheets, ([text, { url, ranges }]) => ({ text, url, ranges }))
99+
return Array.from(checked_stylesheets, ([text, { url, ranges }]) => ({
100+
text,
101+
url,
102+
ranges: concatenate(dedupe_list(ranges.sort((a, b) => a.start - b.start))).sort((a, b) => a.start - b.start),
103+
}))
40104
}

0 commit comments

Comments
 (0)