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