@@ -38,6 +38,114 @@ function ratio(fraction: number, total: number) {
3838 return fraction / total
3939}
4040
41+ function calculate_stylesheet_coverage ( { text, url, ranges } : Coverage ) {
42+ function is_line_covered ( line : string , start_offset : number ) {
43+ let end = start_offset + line . length
44+ let next_offset = end + 1 // account for newline character
45+ let is_empty = / ^ \s * $ / . test ( line )
46+ let is_closing_brace = line . endsWith ( '}' )
47+
48+ if ( ! is_empty && ! is_closing_brace ) {
49+ for ( let range of ranges ) {
50+ if ( range . start > end || range . end < start_offset ) {
51+ continue
52+ }
53+ if ( range . start <= start_offset && range . end >= end ) {
54+ return true
55+ } else if ( line . startsWith ( '@' ) && range . start > start_offset && range . start < next_offset ) {
56+ return true
57+ }
58+ }
59+ }
60+ return false
61+ }
62+
63+ let lines = text . split ( '\n' )
64+ let total_file_lines = lines . length
65+ let line_coverage = new Uint8Array ( total_file_lines )
66+ let file_lines_covered = 0
67+ let file_total_bytes = text . length
68+ let file_bytes_covered = 0
69+ let offset = 0
70+
71+ for ( let index = 0 ; index < lines . length ; index ++ ) {
72+ let line = lines [ index ] !
73+ let start = offset
74+ let end = offset + line . length
75+ let next_offset = end + 1 // +1 for the newline character
76+ let is_empty = / ^ \s * $ / . test ( line )
77+ let is_closing_brace = line . endsWith ( '}' )
78+ let is_in_range = is_line_covered ( line , start )
79+ let is_covered = false
80+
81+ let prev_is_covered = index > 0 && line_coverage [ index - 1 ] === 1
82+
83+ if ( is_in_range && ! is_closing_brace && ! is_empty ) {
84+ is_covered = true
85+ } else if ( ( is_empty || is_closing_brace ) && prev_is_covered ) {
86+ is_covered = true
87+ } else if ( is_empty && ! prev_is_covered && is_line_covered ( lines [ index + 1 ] ! , next_offset ) ) {
88+ // If the next line is covered, mark this empty line as covered
89+ is_covered = true
90+ }
91+
92+ line_coverage [ index ] = is_covered ? 1 : 0
93+
94+ if ( is_covered ) {
95+ file_lines_covered ++
96+ file_bytes_covered += line . length + 1
97+ }
98+
99+ offset = next_offset
100+ }
101+
102+ // Create "chunks" of covered/uncovered lines for easier rendering later on
103+ let chunks = [
104+ {
105+ start_line : 1 ,
106+ is_covered : line_coverage [ 0 ] === 1 ,
107+ end_line : 1 ,
108+ total_lines : 1 ,
109+ } ,
110+ ]
111+
112+ for ( let index = 1 ; index < line_coverage . length ; index ++ ) {
113+ let is_covered = line_coverage . at ( index )
114+ if ( is_covered !== line_coverage . at ( index - 1 ) ) {
115+ let last_chunk = chunks . at ( - 1 ) !
116+ last_chunk . end_line = index
117+ last_chunk . total_lines = index - last_chunk . start_line + 1
118+
119+ chunks . push ( {
120+ start_line : index + 1 ,
121+ is_covered : is_covered === 1 ,
122+ end_line : index ,
123+ total_lines : 0 ,
124+ } )
125+ }
126+ }
127+
128+ let last_chunk = chunks . at ( - 1 ) !
129+ last_chunk . total_lines = line_coverage . length + 1 - last_chunk . start_line
130+ last_chunk . end_line = line_coverage . length
131+
132+ return {
133+ url,
134+ text,
135+ ranges,
136+ unused_bytes : file_total_bytes - file_bytes_covered ,
137+ used_bytes : file_bytes_covered ,
138+ total_bytes : file_total_bytes ,
139+ line_coverage_ratio : ratio ( file_lines_covered , total_file_lines ) ,
140+ byte_coverage_ratio : ratio ( file_bytes_covered , file_total_bytes ) ,
141+ line_coverage,
142+ total_lines : total_file_lines ,
143+ covered_lines : file_lines_covered ,
144+ uncovered_lines : total_file_lines - file_lines_covered ,
145+ chunks,
146+ }
147+ }
148+
41149/**
42150 * @description
43151 * CSS Code Coverage calculation
@@ -55,115 +163,7 @@ export async function calculate_coverage(coverage: Coverage[]): Promise<Coverage
55163 let filtered_coverage : Coverage [ ] = await filter_coverage ( coverage )
56164 let prettified_coverage : Coverage [ ] = prettify ( filtered_coverage )
57165 let deduplicated : Coverage [ ] = deduplicate_entries ( prettified_coverage )
58-
59- // Calculate coverage for each individual stylesheet we found
60- let coverage_per_stylesheet = deduplicated . map ( ( { text, url, ranges } ) => {
61- function is_line_covered ( line : string , start_offset : number ) {
62- let end = start_offset + line . length
63- let next_offset = end + 1 // account for newline character
64- let is_empty = / ^ \s * $ / . test ( line )
65- let is_closing_brace = line . endsWith ( '}' )
66-
67- if ( ! is_empty && ! is_closing_brace ) {
68- for ( let range of ranges ) {
69- if ( range . start > end || range . end < start_offset ) {
70- continue
71- }
72- if ( range . start <= start_offset && range . end >= end ) {
73- return true
74- } else if ( line . startsWith ( '@' ) && range . start > start_offset && range . start < next_offset ) {
75- return true
76- }
77- }
78- }
79- return false
80- }
81-
82- let lines = text . split ( '\n' )
83- let total_file_lines = lines . length
84- let line_coverage = new Uint8Array ( total_file_lines )
85- let file_lines_covered = 0
86- let file_total_bytes = text . length
87- let file_bytes_covered = 0
88- let offset = 0
89-
90- for ( let index = 0 ; index < lines . length ; index ++ ) {
91- let line = lines [ index ] !
92- let start = offset
93- let end = offset + line . length
94- let next_offset = end + 1 // +1 for the newline character
95- let is_empty = / ^ \s * $ / . test ( line )
96- let is_closing_brace = line . endsWith ( '}' )
97- let is_in_range = is_line_covered ( line , start )
98- let is_covered = false
99-
100- let prev_is_covered = index > 0 && line_coverage [ index - 1 ] === 1
101-
102- if ( is_in_range && ! is_closing_brace && ! is_empty ) {
103- is_covered = true
104- } else if ( ( is_empty || is_closing_brace ) && prev_is_covered ) {
105- is_covered = true
106- } else if ( is_empty && ! prev_is_covered && is_line_covered ( lines [ index + 1 ] ! , next_offset ) ) {
107- // If the next line is covered, mark this empty line as covered
108- is_covered = true
109- }
110-
111- line_coverage [ index ] = is_covered ? 1 : 0
112-
113- if ( is_covered ) {
114- file_lines_covered ++
115- file_bytes_covered += line . length + 1
116- }
117-
118- offset = next_offset
119- }
120-
121- // Create "chunks" of covered/uncovered lines for easier rendering later on
122- let chunks = [
123- {
124- start_line : 1 ,
125- is_covered : line_coverage [ 0 ] === 1 ,
126- end_line : 1 ,
127- total_lines : 1 ,
128- } ,
129- ]
130-
131- for ( let index = 1 ; index < line_coverage . length ; index ++ ) {
132- let is_covered = line_coverage . at ( index )
133- if ( is_covered !== line_coverage . at ( index - 1 ) ) {
134- let last_chunk = chunks . at ( - 1 ) !
135- last_chunk . end_line = index
136- last_chunk . total_lines = index - last_chunk . start_line + 1
137-
138- chunks . push ( {
139- start_line : index + 1 ,
140- is_covered : is_covered === 1 ,
141- end_line : index ,
142- total_lines : 0 ,
143- } )
144- }
145- }
146-
147- let last_chunk = chunks . at ( - 1 ) !
148- last_chunk . total_lines = line_coverage . length + 1 - last_chunk . start_line
149- last_chunk . end_line = line_coverage . length
150-
151- return {
152- url,
153- text,
154- ranges,
155- unused_bytes : file_total_bytes - file_bytes_covered ,
156- used_bytes : file_bytes_covered ,
157- total_bytes : file_total_bytes ,
158- line_coverage_ratio : ratio ( file_lines_covered , total_file_lines ) ,
159- byte_coverage_ratio : ratio ( file_bytes_covered , file_total_bytes ) ,
160- line_coverage,
161- total_lines : total_file_lines ,
162- covered_lines : file_lines_covered ,
163- uncovered_lines : total_file_lines - file_lines_covered ,
164- chunks,
165- }
166- } )
166+ let coverage_per_stylesheet = deduplicated . map ( ( stylesheet ) => calculate_stylesheet_coverage ( stylesheet ) )
167167
168168 // Calculate total coverage for all stylesheets combined
169169 let { total_lines, total_covered_lines, total_uncovered_lines, total_bytes, total_used_bytes, total_unused_bytes } =
0 commit comments