Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 1 addition & 19 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,5 @@
"default-case": "off",
"no-rest-spread-properties": "off",
"require-await": "warn"
},
"overrides": [
{
"files": [
"*.svelte"
],
"rules": {
"no-unassigned-vars": "off"
}
},
{
"files": [
".stylelintrc.mjs"
],
"rules": {
"unicorn/no-null": "off"
}
}
]
}
}
2 changes: 1 addition & 1 deletion src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function cli(cli_args: string[]) {
const args = parse_arguments(cli_args)
let params = validate_arguments(args)
let coverage_data = await read(params['coverage-dir'])
let report = program(
let report = await program(
{
min_file_coverage: params['min-line-coverage'],
min_file_line_coverage: params['min-file-line-coverage'],
Expand Down
9 changes: 2 additions & 7 deletions src/cli/program.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import { calculate_coverage, type Coverage, type CoverageResult } from '../lib/index.js'
import { DOMParser } from 'linkedom'

function parse_html(html: string) {
return new DOMParser().parseFromString(html, 'text/html')
}

export class MissingDataError extends Error {
constructor() {
Expand Down Expand Up @@ -54,7 +49,7 @@ function validate_min_file_line_coverage(actual: number, expected: number | unde
}
}

export function program(
export async function program(
{
min_file_coverage,
min_file_line_coverage,
Expand All @@ -67,7 +62,7 @@ export function program(
if (coverage_data.length === 0) {
throw new MissingDataError()
}
let coverage = calculate_coverage(coverage_data, parse_html)
let coverage = await calculate_coverage(coverage_data)
let min_line_coverage_result = validate_min_line_coverage(coverage.line_coverage_ratio, min_file_coverage)
let min_file_line_coverage_result = validate_min_file_line_coverage(
Math.min(...coverage.coverage_per_stylesheet.map((sheet) => sheet.line_coverage_ratio)),
Expand Down
32 changes: 8 additions & 24 deletions src/lib/filter-entries.test.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import { test, expect } from '@playwright/test'
import { filter_coverage } from './filter-entries.js'
import { DOMParser } from 'linkedom'

function html_parser(html: string) {
return new DOMParser().parseFromString(html, 'text/html')
}

test('filters out JS files', () => {
test('filters out JS files', async () => {
let entries = [
{
url: 'http://example.com/script.js',
text: 'console.log("Hello world")',
ranges: [{ start: 0, end: 25 }],
},
]
expect(filter_coverage(entries, html_parser)).toEqual([])
expect(await filter_coverage(entries)).toEqual([])
})

test('keeps files with CSS extension', () => {
test('keeps files with CSS extension', async () => {
let entries = [
{
url: 'http://example.com/styles.css',
text: 'a{color:red}',
ranges: [{ start: 0, end: 13 }],
},
]
expect(filter_coverage(entries, html_parser)).toEqual(entries)
expect(await filter_coverage(entries)).toEqual(entries)
})

test('keeps extension-less URL with HTML text', () => {
test('keeps extension-less URL with HTML text', async () => {
let entries = [
{
url: 'http://example.com',
Expand All @@ -43,27 +38,16 @@ test('keeps extension-less URL with HTML text', () => {
ranges: [{ start: 0, end: 13 }], // ranges are remapped
},
]
expect(filter_coverage(entries, html_parser)).toEqual(expected)
expect(await filter_coverage(entries)).toEqual(expected)
})

test('keeps extension-less URL with CSS text (running coverage in vite dev mode)', () => {
test('keeps extension-less URL with CSS text (running coverage in vite dev mode)', async () => {
let entries = [
{
url: 'http://example.com',
text: 'a{color:red;}',
ranges: [{ start: 0, end: 13 }],
},
]
expect(filter_coverage(entries, html_parser)).toEqual(entries)
})

test('skips extension-less URL with HTML text when no parser is provided', () => {
let entries = [
{
url: 'http://example.com',
text: `<html><style>a{color:red;}</style></html>`,
ranges: [{ start: 13, end: 26 }],
},
]
expect(filter_coverage(entries)).toEqual([])
expect(await filter_coverage(entries)).toEqual(entries)
})
10 changes: 2 additions & 8 deletions src/lib/filter-entries.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { Coverage } from './parse-coverage.js'
import { ext } from './ext.js'
import type { Parser } from './types.js'
import { remap_html } from './remap-html.js'

function is_html(text: string): boolean {
return /<\/?(html|body|head|div|span|script|style)/i.test(text)
}

export function filter_coverage(coverage: Coverage[], parse_html?: Parser): Coverage[] {
export async function filter_coverage(coverage: Coverage[]): Promise<Coverage[]> {
let result = []

for (let entry of coverage) {
Expand All @@ -21,12 +20,7 @@ export function filter_coverage(coverage: Coverage[], parse_html?: Parser): Cove
}

if (is_html(entry.text)) {
if (!parse_html) {
// No parser provided, cannot extract CSS from HTML, silently skip this entry
continue
}

let { css, ranges } = remap_html(parse_html, entry.text, entry.ranges)
let { css, ranges } = await remap_html(entry.text, entry.ranges)
result.push({
url: entry.url,
text: css,
Expand Down
117 changes: 39 additions & 78 deletions src/lib/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ import { generate_coverage } from './test/generate-coverage.js'
import { calculate_coverage } from './index.js'
import type { Coverage } from './parse-coverage.js'
import { format } from '@projectwallace/format-css'
import { DOMParser } from 'linkedom'

function html_parser(html: string) {
return new DOMParser().parseFromString(html, 'text/html')
}

test.describe('from <style> tag', () => {
let coverage: Coverage[]
Expand All @@ -32,8 +27,8 @@ test.describe('from <style> tag', () => {
coverage = (await generate_coverage(html)) as Coverage[]
})

test('counts totals', () => {
let result = calculate_coverage(coverage, html_parser)
test('counts totals', async () => {
let result = await calculate_coverage(coverage)
expect.soft(result.total_files_found).toBe(1)
expect.soft(result.total_bytes).toBe(80)
expect.soft(result.used_bytes).toBe(42)
Expand All @@ -45,8 +40,8 @@ test.describe('from <style> tag', () => {
expect.soft(result.total_stylesheets).toBe(1)
})

test('calculates stats per stylesheet', () => {
let result = calculate_coverage(coverage, html_parser)
test('calculates stats per stylesheet', async () => {
let result = await calculate_coverage(coverage)
let sheet = result.coverage_per_stylesheet.at(0)!
expect.soft(sheet.url).toBe('http://localhost/test.html')
expect.soft(sheet.ranges).toEqual([
Expand Down Expand Up @@ -89,8 +84,8 @@ test.describe('from <link rel="stylesheet">', () => {
coverage = (await generate_coverage(html, { link_css: css })) as Coverage[]
})

test('counts totals', () => {
let result = calculate_coverage(coverage, html_parser)
test('counts totals', async () => {
let result = await calculate_coverage(coverage)
expect.soft(result.total_files_found).toBe(1)
expect.soft(result.total_bytes).toBe(174)
expect.soft(result.used_bytes).toBe(91)
Expand All @@ -102,8 +97,8 @@ test.describe('from <link rel="stylesheet">', () => {
expect.soft(result.total_stylesheets).toBe(1)
})

test('calculates stats per stylesheet', () => {
let result = calculate_coverage(coverage, html_parser)
test('calculates stats per stylesheet', async () => {
let result = await calculate_coverage(coverage)
let sheet = result.coverage_per_stylesheet.at(0)!
expect.soft(sheet.url).toBe('http://localhost/style.css')
expect.soft(sheet.ranges).toEqual([
Expand Down Expand Up @@ -148,17 +143,17 @@ test.describe('from coverage data downloaded directly from the browser as JSON',
},
]

test('counts totals', () => {
let result = calculate_coverage(coverage, html_parser)
test('counts totals', async () => {
let result = await calculate_coverage(coverage)
expect.soft(result.covered_lines).toBe(9)
expect.soft(result.uncovered_lines).toBe(5)
expect.soft(result.total_lines).toBe(14)
expect.soft(result.line_coverage_ratio).toBe(9 / 14)
expect.soft(result.total_stylesheets).toBe(1)
})

test('extracts and formats css', () => {
let result = calculate_coverage(coverage, html_parser)
test('extracts and formats css', async () => {
let result = await calculate_coverage(coverage)
expect(result.coverage_per_stylesheet.at(0)?.text).toEqual(
format(`h1 {
color: blue;
Expand All @@ -178,8 +173,8 @@ test.describe('from coverage data downloaded directly from the browser as JSON',
)
})

test('calculates line coverage', () => {
let result = calculate_coverage(coverage, html_parser)
test('calculates line coverage', async () => {
let result = await calculate_coverage(coverage)
expect(result.coverage_per_stylesheet.at(0)?.line_coverage).toEqual(
new Uint8Array([
// h1 {}
Expand All @@ -198,8 +193,8 @@ test.describe('from coverage data downloaded directly from the browser as JSON',
)
})

test('calculates chunks', () => {
let result = calculate_coverage(coverage, html_parser)
test('calculates chunks', async () => {
let result = await calculate_coverage(coverage)
expect(result.coverage_per_stylesheet.at(0)?.chunks).toEqual([
{ start_line: 1, is_covered: true, end_line: 4, total_lines: 4 },
{ start_line: 5, is_covered: false, end_line: 8, total_lines: 4 },
Expand All @@ -209,22 +204,19 @@ test.describe('from coverage data downloaded directly from the browser as JSON',
])
})

test('calculates chunks for fully covered file', () => {
let result = calculate_coverage(
[
{
url: 'https://example.com',
ranges: [
{
start: 0,
end: 19,
},
],
text: 'h1 { color: blue; }',
},
],
html_parser,
)
test('calculates chunks for fully covered file', async () => {
let result = await calculate_coverage([
{
url: 'https://example.com',
ranges: [
{
start: 0,
end: 19,
},
],
text: 'h1 { color: blue; }',
},
])
expect(result.coverage_per_stylesheet.at(0)?.text).toEqual('h1 {\n\tcolor: blue;\n}')
expect(result.coverage_per_stylesheet.at(0)?.chunks).toEqual([
{
Expand All @@ -236,17 +228,14 @@ test.describe('from coverage data downloaded directly from the browser as JSON',
])
})

test('calculates chunks for fully uncovered file', () => {
let result = calculate_coverage(
[
{
url: 'https://example.com',
ranges: [],
text: 'h1 { color: blue; }',
},
],
html_parser,
)
test('calculates chunks for fully uncovered file', async () => {
let result = await calculate_coverage([
{
url: 'https://example.com',
ranges: [],
text: 'h1 { color: blue; }',
},
])
expect(result.coverage_per_stylesheet.at(0)?.chunks).toEqual([
{
start_line: 1,
Expand All @@ -258,8 +247,8 @@ test.describe('from coverage data downloaded directly from the browser as JSON',
})
})

test('handles empty input', () => {
let result = calculate_coverage([], html_parser)
test('handles empty input', async () => {
let result = await calculate_coverage([])
expect(result.total_files_found).toBe(0)
expect(result.total_bytes).toBe(0)
expect(result.used_bytes).toBe(0)
Expand All @@ -271,31 +260,3 @@ test('handles empty input', () => {
expect(result.total_stylesheets).toBe(0)
expect(result.coverage_per_stylesheet).toEqual([])
})

test.describe('garbage input', () => {
test('garbage Array', () => {
expect(() =>
calculate_coverage(
[
{
test: 1,
garbage: true,
},
] as unknown as Coverage[],
html_parser,
),
).toThrow('No valid coverage data found')
})

test('garbage Object', () => {
expect(() =>
calculate_coverage(
{
test: 1,
garbage: true,
} as unknown as Coverage[],
html_parser,
),
).toThrow('No valid coverage data found')
})
})
12 changes: 3 additions & 9 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { is_valid_coverage, type Coverage, type Range } from './parse-coverage.js'
import { type Coverage, type Range } from './parse-coverage.js'
import { prettify } from './prettify.js'
import { deduplicate_entries } from './decuplicate.js'
import { filter_coverage } from './filter-entries.js'
import type { Parser } from './types.js'

export type CoverageData = {
unused_bytes: number
Expand Down Expand Up @@ -50,14 +49,10 @@ function ratio(fraction: number, total: number) {
* 4. Calculate used/unused CSS bytes (fastest path, no inspection of the actual CSS needed)
* 5. Calculate line-coverage, byte-coverage per stylesheet
*/
export function calculate_coverage(coverage: Coverage[], parse_html?: Parser): CoverageResult {
export async function calculate_coverage(coverage: Coverage[]): Promise<CoverageResult> {
let total_files_found = coverage.length

if (!is_valid_coverage(coverage)) {
throw new TypeError('No valid coverage data found')
}

let filtered_coverage: Coverage[] = filter_coverage(coverage, parse_html)
let filtered_coverage: Coverage[] = await filter_coverage(coverage)
let prettified_coverage: Coverage[] = prettify(filtered_coverage)
let deduplicated: Coverage[] = deduplicate_entries(prettified_coverage)

Expand Down Expand Up @@ -209,4 +204,3 @@ export function calculate_coverage(coverage: Coverage[], parse_html?: Parser): C

export type { Coverage, Range } from './parse-coverage.js'
export { parse_coverage } from './parse-coverage.js'
export type { Parser } from './types.js'
Loading