@projectwallace/css-code-coverage 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,224 +1,247 @@
1
- import * as g from "valibot";
2
- import { format as x } from "@projectwallace/format-css";
3
- let m = g.array(
4
- g.object({
5
- text: g.string(),
6
- url: g.string(),
7
- ranges: g.array(
8
- g.object({
9
- start: g.number(),
10
- end: g.number()
1
+ import * as h from "valibot";
2
+ import { format as F } from "@projectwallace/format-css";
3
+ import { tokenTypes as x, tokenize as $ } from "css-tree/tokenizer";
4
+ let J = h.array(
5
+ h.object({
6
+ text: h.optional(h.string()),
7
+ url: h.string(),
8
+ ranges: h.array(
9
+ h.object({
10
+ start: h.number(),
11
+ end: h.number()
11
12
  })
12
13
  )
13
14
  })
14
15
  );
15
- function p(e) {
16
- return g.safeParse(m, e).success;
16
+ function q(t) {
17
+ return h.safeParse(J, t).success;
17
18
  }
18
- function M(e) {
19
+ function X(t) {
19
20
  try {
20
- let t = JSON.parse(e);
21
- return p(t) ? t : [];
21
+ let e = JSON.parse(t);
22
+ return q(e) ? e : [];
22
23
  } catch {
23
24
  return [];
24
25
  }
25
26
  }
26
- function k(e) {
27
- let t = /* @__PURE__ */ new Map();
28
- for (let n of e) {
29
- let r = n.text;
30
- if (t.has(r)) {
31
- let l = t.get(r).ranges;
32
- for (let a of n.ranges) {
33
- let s = !1;
34
- for (let f of l)
35
- if (f.start === a.start && f.end === a.end) {
36
- s = !0;
27
+ function P(t) {
28
+ return t.map(({ url: e, text: s, ranges: n }) => {
29
+ if (!s)
30
+ return { url: e, text: s, ranges: n };
31
+ let c = F(s), f = /* @__PURE__ */ new Set([
32
+ x.EOF,
33
+ x.BadString,
34
+ x.BadUrl,
35
+ x.WhiteSpace,
36
+ x.Semicolon,
37
+ x.Comment,
38
+ x.Colon
39
+ ]), o = n.map(({ start: a, end: i }) => ({ start: a, end: i, tokens: [] }));
40
+ function v(a, i) {
41
+ let r = 0;
42
+ for (let u of o) {
43
+ if (u.start > i) return -1;
44
+ if (u.start <= a && u.end >= i)
45
+ return r;
46
+ r++;
47
+ }
48
+ return -1;
49
+ }
50
+ let _ = 0;
51
+ $(s, (a, i, r) => {
52
+ if (f.has(a)) return;
53
+ _++, a === x.Url && (_ += 2);
54
+ let u = v(i, r);
55
+ u !== -1 && o[u].tokens.push(_);
56
+ });
57
+ let g = /* @__PURE__ */ new Map();
58
+ _ = 0, $(c, (a, i, r) => {
59
+ f.has(a) || (_++, a === x.Url && (_ += 2), g.set(_, { start: i, end: r }));
60
+ });
61
+ let y = [];
62
+ for (let a of o) {
63
+ let i = g.get(a.tokens.at(0)), r = g.get(a.tokens.at(-1));
64
+ i !== void 0 && r !== void 0 && y.push({
65
+ start: i.start,
66
+ end: r.end
67
+ });
68
+ }
69
+ return { url: e, text: c, ranges: y };
70
+ });
71
+ }
72
+ function R(t) {
73
+ let e = /* @__PURE__ */ new Map();
74
+ for (let s of t) {
75
+ let n = s.text || "";
76
+ if (e.has(n)) {
77
+ let f = e.get(n).ranges;
78
+ for (let o of s.ranges) {
79
+ let v = !1;
80
+ for (let _ of f)
81
+ if (_.start === o.start && _.end === o.end) {
82
+ v = !0;
37
83
  break;
38
84
  }
39
- s || l.push(a);
85
+ v || f.push(o);
40
86
  }
41
87
  } else
42
- t.set(r, {
43
- url: n.url,
44
- ranges: n.ranges
88
+ e.set(n, {
89
+ url: s.url,
90
+ ranges: s.ranges
45
91
  });
46
92
  }
47
- return Array.from(t, ([n, { url: r, ranges: o }]) => ({ text: n, url: r, ranges: o }));
93
+ return e;
48
94
  }
49
- function A(e) {
95
+ function D(t) {
50
96
  try {
51
- let t = new URL(e);
52
- return t.pathname.slice(t.pathname.lastIndexOf(".") + 1);
97
+ let e = new URL(t);
98
+ return e.pathname.slice(e.pathname.lastIndexOf(".") + 1);
53
99
  } catch {
54
- let t = e.lastIndexOf(".");
55
- return e.slice(t, e.indexOf("/", t) + 1);
100
+ let e = t.lastIndexOf(".");
101
+ return t.slice(e, t.indexOf("/", e) + 1);
56
102
  }
57
103
  }
58
- function w(e, t, n) {
59
- let r = e(t), o = "", l = [], a = 0, s = r.querySelectorAll("style");
60
- for (let f of Array.from(s)) {
61
- let c = f.textContent;
62
- if (!c.trim()) continue;
63
- o += c;
64
- let i = t.indexOf(c), d = i + c.length;
65
- for (let _ of n)
66
- _.start >= i && _.end <= d && l.push({
67
- start: a + (_.start - i),
68
- end: a + (_.end - i)
104
+ function G(t, e, s) {
105
+ let n = t(e), c = "", f = [], o = 0, v = n.querySelectorAll("style");
106
+ for (let _ of Array.from(v)) {
107
+ let g = _.textContent;
108
+ if (!g.trim()) continue;
109
+ c += g;
110
+ let y = e.indexOf(g), a = y + g.length;
111
+ for (let i of s)
112
+ i.start >= y && i.end <= a && f.push({
113
+ start: o + (i.start - y),
114
+ end: o + (i.end - y)
69
115
  });
70
- a += c.length;
116
+ o += g.length;
71
117
  }
72
118
  return {
73
- css: o,
74
- ranges: l
119
+ css: c,
120
+ ranges: f
75
121
  };
76
122
  }
77
- function E(e) {
78
- return /<\/?(html|body|head|div|span|script|style)/i.test(e);
123
+ function H(t) {
124
+ return /<\/?(html|body|head|div|span|script|style)/i.test(t);
79
125
  }
80
- function O(e, t) {
81
- let n = [];
82
- for (let r of e) {
83
- let o = A(r.url).toLowerCase();
84
- if (o !== "js") {
85
- if (o === "css") {
86
- n.push(r);
126
+ function K(t, e) {
127
+ let s = [];
128
+ for (let n of t) {
129
+ if (!n.text) continue;
130
+ let c = D(n.url).toLowerCase();
131
+ if (c !== "js") {
132
+ if (c === "css") {
133
+ s.push(n);
87
134
  continue;
88
135
  }
89
- if (E(r.text)) {
90
- if (!t)
136
+ if (H(n.text)) {
137
+ if (!e)
91
138
  continue;
92
- let { css: l, ranges: a } = w(t, r.text, r.ranges);
93
- n.push({
94
- url: r.url,
95
- text: l,
96
- ranges: a
139
+ let { css: f, ranges: o } = G(e, n.text, n.ranges);
140
+ s.push({
141
+ url: n.url,
142
+ text: f,
143
+ ranges: o
97
144
  });
98
145
  continue;
99
146
  }
100
- n.push({
101
- url: r.url,
102
- text: r.text,
103
- ranges: r.ranges
147
+ s.push({
148
+ url: n.url,
149
+ text: n.text,
150
+ ranges: n.ranges
104
151
  });
105
152
  }
106
153
  }
107
- return n;
154
+ return s;
108
155
  }
109
- function L(e) {
110
- let t = [], n = 0;
111
- for (let r of e.ranges)
112
- n !== r.start && (t.push({
113
- start_offset: n,
114
- end_offset: r.start,
115
- is_covered: !1
116
- }), n = r.start), t.push({
117
- start_offset: r.start,
118
- end_offset: r.end,
119
- is_covered: !0
120
- }), n = r.end;
121
- return n !== e.text.length && t.push({
122
- start_offset: n,
123
- end_offset: e.text.length,
124
- is_covered: !1
125
- }), {
126
- ...e,
127
- chunks: t
128
- };
156
+ function j(t, e) {
157
+ return e === 0 ? 0 : t / e;
129
158
  }
130
- function N(e) {
131
- for (let n of e)
132
- for (let r of n.ranges)
133
- for (let o = 1; o >= -28; o--) {
134
- let l = r.start + o;
135
- if (n.text.charAt(l) === "@") {
136
- r.start = l;
137
- break;
138
- }
159
+ function Y(t, e) {
160
+ let s = t.length;
161
+ if (!q(t))
162
+ throw new TypeError("No valid coverage data found");
163
+ let n = K(t, e), c = P(n), f = R(c), o = Array.from(f).map(([r, { url: u, ranges: L }]) => {
164
+ function M(l, d) {
165
+ let b = d + l.length, N = b + 1, W = /^\s*$/.test(l), k = l.endsWith("}");
166
+ if (!W && !k) {
167
+ for (let m of L)
168
+ if (!(m.start > b || m.end < d)) {
169
+ if (m.start <= d && m.end >= b)
170
+ return !0;
171
+ if (l.startsWith("@") && m.start > d && m.start < N)
172
+ return !0;
173
+ }
139
174
  }
140
- return e;
141
- }
142
- function S(e) {
143
- let t = 1, n = 0, r = e.chunks.map((l, a) => {
144
- let s = x(e.text.slice(l.start_offset, l.end_offset));
145
- l.is_covered && (a === 0 ? s = s + `
146
- ` : a === e.chunks.length - 1 ? s = `
147
- ` + s : s = `
148
- ` + s + `
149
- `);
150
- let f = s.split(`
151
- `).length, c = n, i = Math.max(n + s.length - 1, 0), d = t, _ = t + f;
152
- return t = _, n = i, {
153
- ...l,
154
- start_offset: c,
155
- start_line: d,
156
- end_line: _ - 1,
157
- end_offset: i,
158
- css: s,
159
- total_lines: _ - d
175
+ return !1;
176
+ }
177
+ let S = r.split(`
178
+ `), O = S.length, p = new Uint8Array(O), C = 0, B = r.length, U = 0, E = 0;
179
+ for (let l = 0; l < S.length; l++) {
180
+ let d = S[l], b = E, W = E + d.length + 1, k = /^\s*$/.test(d), m = d.endsWith("}"), z = M(d, b), w = !1, T = l > 0 && p[l - 1] === 1;
181
+ (z && !m && !k || (k || m) && T || k && !T && M(S[l + 1], W)) && (w = !0), p[l] = w ? 1 : 0, w && (C++, U += d.length + 1), E = W;
182
+ }
183
+ let A = [
184
+ {
185
+ start_line: 1,
186
+ is_covered: p[0] === 1,
187
+ end_line: 1,
188
+ total_lines: 1
189
+ }
190
+ ];
191
+ for (let l = 1; l < p.length; l++) {
192
+ let d = p[l];
193
+ if (d !== p[l - 1]) {
194
+ let b = A.at(-1);
195
+ b.end_line = l, b.total_lines = l - b.start_line + 1, A.push({
196
+ start_line: l + 1,
197
+ is_covered: d === 1,
198
+ end_line: l,
199
+ total_lines: 0
200
+ });
201
+ }
202
+ }
203
+ let I = A.at(-1);
204
+ return I.total_lines = p.length + 1 - I.start_line, I.end_line = p.length, {
205
+ url: u,
206
+ text: r,
207
+ ranges: L,
208
+ unused_bytes: B - U,
209
+ used_bytes: U,
210
+ total_bytes: B,
211
+ line_coverage_ratio: j(C, O),
212
+ byte_coverage_ratio: j(U, B),
213
+ line_coverage: p,
214
+ total_lines: O,
215
+ covered_lines: C,
216
+ uncovered_lines: O - C,
217
+ chunks: A
160
218
  };
161
- });
162
- return {
163
- ...e,
164
- // TODO: update ranges as well?? Or remove them because we have chunks now
165
- chunks: r,
166
- text: r.map(({ css: l }) => l).join("")
167
- };
168
- }
169
- function y(e, t) {
170
- return t === 0 ? 0 : e / t;
171
- }
172
- function T(e) {
173
- let { text: t, url: n, chunks: r } = e, o = 0, l = 0, a = 0, s = 0, f = 0, c = 0;
174
- for (let i of r) {
175
- let d = i.total_lines, _ = i.end_offset - i.start_offset;
176
- s += d, a += _, i.is_covered ? (f += d, l += _) : (c += d, o += _);
177
- }
178
- return {
179
- url: n,
180
- text: t,
181
- uncovered_bytes: o,
182
- covered_bytes: l,
183
- total_bytes: a,
184
- line_coverage_ratio: y(f, s),
185
- byte_coverage_ratio: y(l, a),
186
- total_lines: s,
187
- covered_lines: f,
188
- uncovered_lines: c,
189
- chunks: r
190
- };
191
- }
192
- function C(e, t) {
193
- let n = e.length;
194
- if (!p(e))
195
- throw new TypeError("No valid coverage data found");
196
- let r = O(e, t), o = k(r), f = N(o).map((u) => L(u)).map((u) => S(u)).map((u) => T(u)), { total_lines: c, total_covered_lines: i, total_uncovered_lines: d, total_bytes: _, total_covered_bytes: h, total_uncovered_bytes: b } = f.reduce(
197
- (u, v) => (u.total_lines += v.total_lines, u.total_covered_lines += v.covered_lines, u.total_uncovered_lines += v.uncovered_lines, u.total_bytes += v.total_bytes, u.total_covered_bytes += v.covered_bytes, u.total_uncovered_bytes += v.uncovered_bytes, u),
219
+ }), { total_lines: v, total_covered_lines: _, total_uncovered_lines: g, total_bytes: y, total_used_bytes: a, total_unused_bytes: i } = o.reduce(
220
+ (r, u) => (r.total_lines += u.total_lines, r.total_covered_lines += u.covered_lines, r.total_uncovered_lines += u.uncovered_lines, r.total_bytes += u.total_bytes, r.total_used_bytes += u.used_bytes, r.total_unused_bytes += u.unused_bytes, r),
198
221
  {
199
222
  total_lines: 0,
200
223
  total_covered_lines: 0,
201
224
  total_uncovered_lines: 0,
202
225
  total_bytes: 0,
203
- total_covered_bytes: 0,
204
- total_uncovered_bytes: 0
226
+ total_used_bytes: 0,
227
+ total_unused_bytes: 0
205
228
  }
206
229
  );
207
230
  return {
208
- total_files_found: n,
209
- total_bytes: _,
210
- total_lines: c,
211
- covered_bytes: h,
212
- covered_lines: i,
213
- uncovered_bytes: b,
214
- uncovered_lines: d,
215
- byte_coverage_ratio: y(h, _),
216
- line_coverage_ratio: y(i, c),
217
- coverage_per_stylesheet: f,
218
- total_stylesheets: f.length
231
+ total_files_found: s,
232
+ total_bytes: y,
233
+ total_lines: v,
234
+ used_bytes: a,
235
+ covered_lines: _,
236
+ unused_bytes: i,
237
+ uncovered_lines: g,
238
+ byte_coverage_ratio: j(a, y),
239
+ line_coverage_ratio: j(_, v),
240
+ coverage_per_stylesheet: o,
241
+ total_stylesheets: o.length
219
242
  };
220
243
  }
221
244
  export {
222
- C as calculate_coverage,
223
- M as parse_coverage
245
+ Y as calculate_coverage,
246
+ X as parse_coverage
224
247
  };
@@ -6,4 +6,4 @@ import { Coverage } from './parse-coverage.ts';
6
6
  * - if a duplicate stylesheet enters the room, we add it's ranges to the existing stylesheet's ranges
7
7
  * - only bytes of deduplicated stylesheets are counted
8
8
  */
9
- export declare function deduplicate_entries(entries: Coverage[]): Coverage[];
9
+ export declare function deduplicate_entries(entries: Coverage[]): Map<NonNullable<Coverage['text']>, Pick<Coverage, 'ranges' | 'url'>>;
@@ -1,9 +1,8 @@
1
- import { Coverage } from './parse-coverage.ts';
1
+ import { Coverage, Range } from './parse-coverage.ts';
2
2
  import { Parser } from './types.ts';
3
- import { PrettifiedChunk } from './prettify.ts';
4
3
  export type CoverageData = {
5
- uncovered_bytes: number;
6
- covered_bytes: number;
4
+ unused_bytes: number;
5
+ used_bytes: number;
7
6
  total_bytes: number;
8
7
  line_coverage_ratio: number;
9
8
  byte_coverage_ratio: number;
@@ -14,7 +13,14 @@ export type CoverageData = {
14
13
  export type StylesheetCoverage = CoverageData & {
15
14
  url: string;
16
15
  text: string;
17
- chunks: PrettifiedChunk[];
16
+ ranges: Range[];
17
+ line_coverage: Uint8Array;
18
+ chunks: {
19
+ is_covered: boolean;
20
+ start_line: number;
21
+ end_line: number;
22
+ total_lines: number;
23
+ }[];
18
24
  };
19
25
  export type CoverageResult = CoverageData & {
20
26
  total_files_found: number;
@@ -4,7 +4,7 @@ export type Range = {
4
4
  };
5
5
  export type Coverage = {
6
6
  url: string;
7
- text: string;
7
+ text?: string;
8
8
  ranges: Range[];
9
9
  };
10
10
  export declare function is_valid_coverage(input: unknown): boolean;
@@ -1,12 +1,2 @@
1
- import { Coverage } from './parse-coverage';
2
- import { ChunkedCoverage } from './chunkify';
3
- export type PrettifiedChunk = ChunkedCoverage['chunks'][0] & {
4
- start_line: number;
5
- end_line: number;
6
- total_lines: number;
7
- css: string;
8
- };
9
- export type PrettifiedCoverage = Omit<Coverage, 'ranges'> & {
10
- chunks: PrettifiedChunk[];
11
- };
12
- export declare function prettify(stylesheet: ChunkedCoverage): PrettifiedCoverage;
1
+ import { Coverage } from './parse-coverage.ts';
2
+ export declare function prettify(coverage: Coverage[]): Coverage[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-code-coverage",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "",
5
5
  "author": "Bart Veneman <bart@projectwallace.com>",
6
6
  "repository": {
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@projectwallace/format-css": "^2.1.1",
55
+ "css-tree": "^3.1.0",
55
56
  "valibot": "^1.1.0"
56
57
  }
57
58
  }
@@ -1,9 +0,0 @@
1
- import { Coverage } from './parse-coverage';
2
- export type ChunkedCoverage = Coverage & {
3
- chunks: {
4
- start_offset: number;
5
- end_offset: number;
6
- is_covered: boolean;
7
- }[];
8
- };
9
- export declare function chunkify_stylesheet(stylesheet: Coverage): ChunkedCoverage;
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- import { Coverage } from './parse-coverage';
2
- /**
3
- * WARNING: mutates the ranges array
4
- */
5
- export declare function extend_ranges(coverage: Coverage[]): Coverage[];
@@ -1 +0,0 @@
1
- export {};