@projectwallace/css-code-coverage 0.8.2 → 0.9.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,47 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs, styleText } from "node:util";
3
- import * as v from "valibot";
3
+ import { join, resolve, sep } from "node:path";
4
4
  import { calculate_coverage } from "@projectwallace/css-code-coverage";
5
5
  import { readFile, readdir, stat } from "node:fs/promises";
6
- import { join } from "node:path";
7
-
8
6
  //#region src/cli/arguments.ts
9
- const show_uncovered_options = {
10
- none: "none",
11
- all: "all",
12
- violations: "violations"
13
- };
14
- const reporters = {
15
- pretty: "pretty",
16
- tap: "tap",
17
- json: "json"
18
- };
19
- let CoverageDirSchema = v.pipe(v.string(), v.nonEmpty());
20
- let RatioPercentageSchema = v.pipe(v.string(), v.transform(Number), v.number(), v.minValue(0), v.maxValue(1));
21
- let ShowUncoveredSchema = v.pipe(v.string(), v.enum(show_uncovered_options));
22
- let ReporterSchema = v.pipe(v.string(), v.enum(reporters));
23
- let CliArgumentsSchema = v.object({
24
- "coverage-dir": CoverageDirSchema,
25
- "min-coverage": RatioPercentageSchema,
26
- "min-file-coverage": v.optional(RatioPercentageSchema),
27
- "show-uncovered": v.optional(ShowUncoveredSchema, show_uncovered_options.violations),
28
- reporter: v.optional(ReporterSchema, reporters.pretty)
29
- });
30
- var InvalidArgumentsError = class extends Error {
31
- issues;
32
- constructor(issues) {
33
- super();
34
- this.issues = issues;
35
- }
36
- };
37
- function validate_arguments(args) {
38
- let parse_result = v.safeParse(CliArgumentsSchema, args);
39
- if (!parse_result.success) throw new InvalidArgumentsError(parse_result.issues.map((issue) => ({
40
- path: issue.path?.map((path) => path.key).join("."),
41
- message: issue.message
42
- })));
43
- return parse_result.output;
44
- }
7
+ const SHOW_UNCOVERED = [
8
+ "none",
9
+ "all",
10
+ "violations"
11
+ ];
12
+ const REPORTERS = [
13
+ "pretty",
14
+ "tap",
15
+ "json"
16
+ ];
45
17
  function parse_arguments(args) {
46
18
  let { values } = parseArgs({
47
19
  args,
@@ -62,9 +34,31 @@ function parse_arguments(args) {
62
34
  }
63
35
  }
64
36
  });
65
- return values;
37
+ let issues = [];
38
+ let coverage_dir = values["coverage-dir"];
39
+ if (!coverage_dir) issues.push("--coverage-dir is required");
40
+ else {
41
+ let resolved = resolve(coverage_dir);
42
+ let cwd = process.cwd();
43
+ if (resolved !== cwd && !resolved.startsWith(cwd + sep)) issues.push("InvalidPath");
44
+ }
45
+ let min_coverage = Number(values["min-coverage"]);
46
+ if (values["min-coverage"] === void 0 || isNaN(min_coverage) || min_coverage < 0 || min_coverage > 1) issues.push("--min-coverage must be a number between 0 and 1");
47
+ let min_file_coverage = Number(values["min-file-coverage"]);
48
+ if (isNaN(min_file_coverage) || min_file_coverage < 0 || min_file_coverage > 1) issues.push("--min-file-coverage must be a number between 0 and 1");
49
+ let show_uncovered = values["show-uncovered"];
50
+ if (!SHOW_UNCOVERED.includes(show_uncovered)) issues.push(`--show-uncovered must be one of: ${SHOW_UNCOVERED.join(", ")}`);
51
+ let reporter = values["reporter"];
52
+ if (!REPORTERS.includes(reporter)) issues.push(`--reporter must be one of: ${REPORTERS.join(", ")}`);
53
+ if (issues.length > 0) throw new Error(issues.join("\n"));
54
+ return {
55
+ "coverage-dir": resolve(coverage_dir),
56
+ "min-coverage": min_coverage,
57
+ "min-file-coverage": min_file_coverage,
58
+ "show-uncovered": show_uncovered,
59
+ reporter
60
+ };
66
61
  }
67
-
68
62
  //#endregion
69
63
  //#region src/cli/program.ts
70
64
  function validate_min_line_coverage(actual, expected) {
@@ -99,30 +93,19 @@ function program({ min_coverage, min_file_coverage }, coverage_data) {
99
93
  }
100
94
  };
101
95
  }
102
-
103
96
  //#endregion
104
97
  //#region src/lib/parse-coverage.ts
105
- let RangeSchema = v.object({
106
- start: v.number(),
107
- end: v.number()
108
- });
109
- let CoverageSchema = v.object({
110
- text: v.string(),
111
- url: v.string(),
112
- ranges: v.array(RangeSchema)
113
- });
114
98
  function is_valid_coverage(input) {
115
- return v.safeParse(v.array(CoverageSchema), input).success;
99
+ return Array.isArray(input) && input.every((item) => typeof item === "object" && item !== null && typeof item.text === "string" && typeof item.url === "string" && Array.isArray(item.ranges) && item.ranges.every((r) => typeof r === "object" && r !== null && typeof r.start === "number" && typeof r.end === "number"));
116
100
  }
117
101
  function parse_coverage(input) {
118
102
  try {
119
- let parse_result = JSON.parse(input);
120
- return is_valid_coverage(parse_result) ? parse_result : [];
103
+ let parsed = JSON.parse(input);
104
+ return is_valid_coverage(parsed) ? parsed : [];
121
105
  } catch {
122
106
  return [];
123
107
  }
124
108
  }
125
-
126
109
  //#endregion
127
110
  //#region src/cli/file-reader.ts
128
111
  async function read(coverage_dir) {
@@ -136,31 +119,55 @@ async function read(coverage_dir) {
136
119
  }
137
120
  return parsed_files;
138
121
  }
139
-
140
122
  //#endregion
141
123
  //#region src/cli/reporters/pretty.ts
142
124
  function indent(line) {
143
125
  return (line || "").replace(/^\t+/, (tabs) => " ".repeat(tabs.length * 4));
144
126
  }
145
- let line_number = (num, covered = true) => `${num.toString().padStart(5, " ")} ${covered ? "" : "━"} `;
127
+ let line_number = (num) => `${num.toString().padStart(5, " ")} │ `;
146
128
  function percentage(ratio, decimals = 2) {
147
129
  return `${(ratio * 100).toFixed(ratio === 1 ? 0 : decimals)}%`;
148
130
  }
149
- function print_lines({ report, context }, params, { styleText: styleText$1, print_width }) {
131
+ function highlight(css, styleText) {
132
+ if (css.trim().startsWith("@")) {
133
+ let at_pos = css.indexOf("@");
134
+ let space_pos = css.indexOf(" ", at_pos);
135
+ let name = css.slice(0, space_pos);
136
+ let is_empty = css.endsWith("{}");
137
+ let prelude = css.slice(space_pos, is_empty ? -2 : -1);
138
+ return [
139
+ styleText("blueBright", name),
140
+ styleText("magentaBright", prelude),
141
+ is_empty ? "{}" : "{"
142
+ ].join("");
143
+ }
144
+ if (css.includes(":") && css.endsWith(";")) return [
145
+ styleText("cyanBright", css.slice(0, css.indexOf(":"))),
146
+ ":",
147
+ css.slice(css.indexOf(":") + 1, css.length - 1),
148
+ ";"
149
+ ].join("");
150
+ if (css.endsWith("{}")) return [styleText("greenBright", css.slice(0, -2)), "{}"].join("");
151
+ if (css.endsWith("}")) return css;
152
+ if (css.trim() === "") return css;
153
+ if (css.endsWith(",")) return [styleText("greenBright", css.slice(0, -1)), ","].join("");
154
+ return [styleText("greenBright", css.slice(0, -1)), "{"].join("");
155
+ }
156
+ function print_lines({ report, context }, params, { styleText, print_width }) {
150
157
  let output = [];
151
- if (report.min_line_coverage.ok) output.push(`${styleText$1(["bold", "green"], "Success")}: total line coverage is ${percentage(report.min_line_coverage.actual)}`);
158
+ if (report.min_line_coverage.ok) output.push(`${styleText(["bold", "green"], "Success")}: total line coverage is ${percentage(report.min_line_coverage.actual)}`);
152
159
  else {
153
160
  let { actual, expected } = report.min_line_coverage;
154
- output.push(`${styleText$1(["bold", "red"], "Failed")}: line coverage is ${percentage(actual)}% which is lower than the threshold of ${expected}`);
161
+ output.push(`${styleText(["bold", "red"], "Failed")}: line coverage is ${percentage(actual)}% which is lower than the threshold of ${expected}`);
155
162
  let lines_to_cover = expected * context.coverage.total_lines - context.coverage.covered_lines;
156
163
  output.push(`Tip: cover ${Math.ceil(lines_to_cover)} more ${lines_to_cover === 1 ? "line" : "lines"} to meet the threshold of ${percentage(expected)}`);
157
164
  }
158
165
  if (report.min_file_line_coverage.expected !== void 0) {
159
- let { expected, actual, ok: ok$1 } = report.min_file_line_coverage;
160
- if (ok$1) output.push(`${styleText$1(["bold", "green"], "Success")}: all files pass minimum line coverage of ${percentage(expected)}`);
166
+ let { expected, actual, ok } = report.min_file_line_coverage;
167
+ if (ok) output.push(`${styleText(["bold", "green"], "Success")}: all files pass minimum line coverage of ${percentage(expected)}`);
161
168
  else {
162
169
  let num_files_failed = context.coverage.coverage_per_stylesheet.filter((sheet) => sheet.line_coverage_ratio < expected).length;
163
- output.push(`${styleText$1(["bold", "red"], "Failed")}: ${num_files_failed} ${num_files_failed === 1 ? "file does" : "files do"} not meet the minimum line coverage of ${percentage(expected)} (minimum coverage was ${percentage(actual)})`);
170
+ output.push(`${styleText(["bold", "red"], "Failed")}: ${num_files_failed} ${num_files_failed === 1 ? "file does" : "files do"} not meet the minimum line coverage of ${percentage(expected)} (minimum coverage was ${percentage(actual)})`);
164
171
  if (params["show-uncovered"] === "none") output.push(` Hint: set --show-uncovered=violations to see which files didn't pass`);
165
172
  }
166
173
  }
@@ -172,33 +179,44 @@ function print_lines({ report, context }, params, { styleText: styleText$1, prin
172
179
  output.push();
173
180
  for (let sheet of context.coverage.coverage_per_stylesheet.sort((a, b) => a.line_coverage_ratio - b.line_coverage_ratio)) if (sheet.line_coverage_ratio !== 1 && params["show-uncovered"] === "all" || min_file_line_coverage !== void 0 && min_file_line_coverage !== 0 && sheet.line_coverage_ratio < min_file_line_coverage && params["show-uncovered"] === "violations") {
174
181
  output.push();
175
- output.push(styleText$1("dim", "─".repeat(print_width)));
176
- output.push(sheet.url);
182
+ output.push(styleText("dim", "─".repeat(print_width)));
183
+ output.push(`File: ${sheet.url}`);
177
184
  output.push(`Coverage: ${percentage(sheet.line_coverage_ratio)}, ${sheet.covered_lines}/${sheet.total_lines} lines covered`);
178
185
  if (min_file_line_coverage && min_file_line_coverage !== 0 && sheet.line_coverage_ratio < min_file_line_coverage) {
179
186
  let lines_to_cover = min_file_line_coverage * sheet.total_lines - sheet.covered_lines;
180
187
  output.push(`Tip: cover ${Math.ceil(lines_to_cover)} more ${lines_to_cover === 1 ? "line" : "lines"} to meet the file threshold of ${percentage(min_file_line_coverage)}`);
181
188
  }
182
- output.push(styleText$1("dim", "─".repeat(print_width)));
189
+ output.push(styleText("dim", "─".repeat(print_width)));
183
190
  let lines = sheet.text.split("\n");
184
- for (let chunk of sheet.chunks.filter((chunk$1) => !chunk$1.is_covered)) {
185
- for (let x = Math.max(chunk.start_line - NUM_LEADING_LINES, 1); x < chunk.start_line; x++) output.push([styleText$1("dim", line_number(x)), styleText$1("dim", indent(lines[x - 1]))].join(""));
186
- for (let i = chunk.start_line; i <= chunk.end_line; i++) output.push([styleText$1("red", line_number(i, false)), indent(lines[i - 1])].join(""));
187
- for (let y = chunk.end_line + 1; y < Math.min(chunk.end_line + NUM_TRAILING_LINES, lines.length); y++) output.push([styleText$1("dim", line_number(y)), styleText$1("dim", indent(lines[y - 1]))].join(""));
188
- output.push();
191
+ for (let chunk of sheet.chunks.filter((chunk) => !chunk.is_covered)) {
192
+ for (let x = Math.max(chunk.start_line - NUM_LEADING_LINES, 1); x < chunk.start_line; x++) output.push([
193
+ " ",
194
+ styleText("dim", line_number(x)),
195
+ styleText("dim", indent(lines[x - 1]))
196
+ ].join(""));
197
+ for (let i = chunk.start_line; i <= chunk.end_line; i++) output.push([
198
+ styleText("red", "▌"),
199
+ styleText("dim", line_number(i)),
200
+ highlight(indent(lines[i - 1]), styleText)
201
+ ].join(""));
202
+ for (let y = chunk.end_line + 1; y < Math.min(chunk.end_line + NUM_TRAILING_LINES + 1, lines.length); y++) output.push([
203
+ " ",
204
+ styleText("dim", line_number(y)),
205
+ styleText("dim", indent(lines[y - 1]))
206
+ ].join(""));
207
+ output.push("");
189
208
  }
190
209
  }
191
210
  }
192
211
  return output;
193
212
  }
194
- function print(report, params) {
213
+ function print$2(report, params) {
195
214
  let logger = report.report.ok ? console.log : console.error;
196
215
  for (let line of print_lines(report, params, {
197
216
  styleText,
198
217
  print_width: process.stdout.columns
199
218
  })) logger(line);
200
219
  }
201
-
202
220
  //#endregion
203
221
  //#region src/cli/reporters/tap.ts
204
222
  function version() {
@@ -218,7 +236,7 @@ function meta(data) {
218
236
  for (let key in data) console.log(` ${key}: ${data[key]}`);
219
237
  console.log(" ...");
220
238
  }
221
- function print$1({ report, context }, params) {
239
+ function print$1({ report, context }, _params) {
222
240
  let total_files = context.coverage.coverage_per_stylesheet.length;
223
241
  let total_checks = total_files + 1;
224
242
  let checks_added = 1;
@@ -254,7 +272,6 @@ function print$1({ report, context }, params) {
254
272
  }
255
273
  }
256
274
  }
257
-
258
275
  //#endregion
259
276
  //#region src/cli/reporters/json.ts
260
277
  function prepare({ report, context }, params) {
@@ -268,7 +285,7 @@ function prepare({ report, context }, params) {
268
285
  context
269
286
  };
270
287
  }
271
- function print$2({ report, context }, params) {
288
+ function print({ report, context }, params) {
272
289
  let logger = report.ok ? console.log : console.error;
273
290
  let data = prepare({
274
291
  context,
@@ -276,7 +293,6 @@ function print$2({ report, context }, params) {
276
293
  }, params);
277
294
  logger(JSON.stringify(data));
278
295
  }
279
-
280
296
  //#endregion
281
297
  //#region src/cli/help.ts
282
298
  function help() {
@@ -314,12 +330,11 @@ ${styleText("bold", "EXAMPLES")}
314
330
  $ css-coverage --coverage-dir=./coverage --min-coverage=0.8 --reporter=json
315
331
  `.trim();
316
332
  }
317
-
318
333
  //#endregion
319
334
  //#region src/cli/cli.ts
320
335
  async function cli(cli_args) {
321
336
  if (!cli_args || cli_args.length === 0 || cli_args.includes("--help") || cli_args.includes("-h")) return console.log(help());
322
- let params = validate_arguments(parse_arguments(cli_args));
337
+ let params = parse_arguments(cli_args);
323
338
  let coverage_data = await read(params["coverage-dir"]);
324
339
  let report = program({
325
340
  min_coverage: params["min-coverage"],
@@ -327,8 +342,8 @@ async function cli(cli_args) {
327
342
  }, coverage_data);
328
343
  if (report.report.ok === false) process.exitCode = 1;
329
344
  if (params.reporter === "tap") return print$1(report, params);
330
- if (params.reporter === "json") return print$2(report, params);
331
- return print(report, params);
345
+ if (params.reporter === "json") return print(report, params);
346
+ return print$2(report, params);
332
347
  }
333
348
  try {
334
349
  await cli(process.argv.slice(2));
@@ -336,6 +351,5 @@ try {
336
351
  console.error(error);
337
352
  process.exit(1);
338
353
  }
339
-
340
354
  //#endregion
341
- export { };
355
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,28 +1,14 @@
1
- import * as v from "valibot";
2
-
3
1
  //#region src/lib/parse-coverage.d.ts
4
- declare let RangeSchema: v.ObjectSchema<{
5
- readonly start: v.NumberSchema<undefined>;
6
- readonly end: v.NumberSchema<undefined>;
7
- }, undefined>;
8
- type Range = v.InferInput<typeof RangeSchema>;
9
- declare let CoverageSchema: v.ObjectSchema<{
10
- readonly text: v.StringSchema<undefined>;
11
- readonly url: v.StringSchema<undefined>;
12
- readonly ranges: v.ArraySchema<v.ObjectSchema<{
13
- readonly start: v.NumberSchema<undefined>;
14
- readonly end: v.NumberSchema<undefined>;
15
- }, undefined>, undefined>;
16
- }, undefined>;
17
- type Coverage = v.InferInput<typeof CoverageSchema>;
18
- declare function parse_coverage(input: string): {
2
+ type Range = {
3
+ start: number;
4
+ end: number;
5
+ };
6
+ type Coverage = {
19
7
  text: string;
20
8
  url: string;
21
- ranges: {
22
- start: number;
23
- end: number;
24
- }[];
25
- }[];
9
+ ranges: Range[];
10
+ };
11
+ declare function parse_coverage(input: string): Coverage[];
26
12
  //#endregion
27
13
  //#region src/lib/chunkify.d.ts
28
14
  type Chunk = {
package/dist/index.js CHANGED
@@ -1,28 +1,17 @@
1
- import * as v from "valibot";
1
+ import { tokenize } from "@projectwallace/css-parser/tokenizer";
2
2
  import { format } from "@projectwallace/format-css";
3
-
4
3
  //#region src/lib/parse-coverage.ts
5
- let RangeSchema = v.object({
6
- start: v.number(),
7
- end: v.number()
8
- });
9
- let CoverageSchema = v.object({
10
- text: v.string(),
11
- url: v.string(),
12
- ranges: v.array(RangeSchema)
13
- });
14
4
  function is_valid_coverage(input) {
15
- return v.safeParse(v.array(CoverageSchema), input).success;
5
+ return Array.isArray(input) && input.every((item) => typeof item === "object" && item !== null && typeof item.text === "string" && typeof item.url === "string" && Array.isArray(item.ranges) && item.ranges.every((r) => typeof r === "object" && r !== null && typeof r.start === "number" && typeof r.end === "number"));
16
6
  }
17
7
  function parse_coverage(input) {
18
8
  try {
19
- let parse_result = JSON.parse(input);
20
- return is_valid_coverage(parse_result) ? parse_result : [];
9
+ let parsed = JSON.parse(input);
10
+ return is_valid_coverage(parsed) ? parsed : [];
21
11
  } catch {
22
12
  return [];
23
13
  }
24
14
  }
25
-
26
15
  //#endregion
27
16
  //#region src/lib/chunkify.ts
28
17
  const WHITESPACE_ONLY_REGEX = /^\s+$/;
@@ -38,7 +27,7 @@ function merge(stylesheet) {
38
27
  latest_chunk.end_offset = chunk.end_offset;
39
28
  previous_chunk = chunk;
40
29
  continue;
41
- } else if (WHITESPACE_ONLY_REGEX.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset)) || chunk.end_offset === chunk.start_offset) {
30
+ } else if (chunk.end_offset === chunk.start_offset) {
42
31
  latest_chunk.end_offset = chunk.end_offset;
43
32
  continue;
44
33
  }
@@ -51,6 +40,48 @@ function merge(stylesheet) {
51
40
  chunks: new_chunks
52
41
  };
53
42
  }
43
+ function mark_comments_as_covered(stylesheet) {
44
+ let new_chunks = [];
45
+ for (let chunk of stylesheet.chunks) {
46
+ if (chunk.is_covered) {
47
+ new_chunks.push(chunk);
48
+ continue;
49
+ }
50
+ let text = stylesheet.text.slice(chunk.start_offset, chunk.end_offset);
51
+ let comments = [];
52
+ for (const _ of tokenize(text, ({ start, end }) => comments.push({
53
+ start,
54
+ end
55
+ })));
56
+ if (comments.length === 0) {
57
+ new_chunks.push(chunk);
58
+ continue;
59
+ }
60
+ let last_end = 0;
61
+ for (let comment of comments) {
62
+ if (comment.start > last_end) new_chunks.push({
63
+ start_offset: chunk.start_offset + last_end,
64
+ end_offset: chunk.start_offset + comment.start,
65
+ is_covered: false
66
+ });
67
+ new_chunks.push({
68
+ start_offset: chunk.start_offset + comment.start,
69
+ end_offset: chunk.start_offset + comment.end,
70
+ is_covered: true
71
+ });
72
+ last_end = comment.end;
73
+ }
74
+ if (last_end < text.length) new_chunks.push({
75
+ start_offset: chunk.start_offset + last_end,
76
+ end_offset: chunk.end_offset,
77
+ is_covered: false
78
+ });
79
+ }
80
+ return merge({
81
+ ...stylesheet,
82
+ chunks: new_chunks
83
+ });
84
+ }
54
85
  function chunkify(stylesheet) {
55
86
  let chunks = [];
56
87
  let offset = 0;
@@ -70,7 +101,7 @@ function chunkify(stylesheet) {
70
101
  });
71
102
  offset = range.end;
72
103
  }
73
- if (offset !== stylesheet.text.length - 1) chunks.push({
104
+ if (offset < stylesheet.text.length) chunks.push({
74
105
  start_offset: offset,
75
106
  end_offset: stylesheet.text.length,
76
107
  is_covered: false
@@ -81,14 +112,13 @@ function chunkify(stylesheet) {
81
112
  chunks
82
113
  });
83
114
  }
84
-
85
115
  //#endregion
86
116
  //#region src/lib/prettify.ts
87
117
  function prettify(stylesheet) {
88
118
  let line = 1;
89
119
  let offset = 0;
90
120
  let pretty_chunks = stylesheet.chunks.map((chunk, index) => {
91
- let css = format(stylesheet.text.substring(chunk.start_offset, chunk.end_offset - 1)).trim();
121
+ let css = format(stylesheet.text.substring(chunk.start_offset, chunk.end_offset)).trim();
92
122
  if (chunk.is_covered) {
93
123
  let is_last_chunk = index === stylesheet.chunks.length - 1;
94
124
  if (index === 0) css = css + (is_last_chunk ? "" : "\n");
@@ -118,7 +148,6 @@ function prettify(stylesheet) {
118
148
  text: pretty_chunks.map(({ css }) => css).join("\n")
119
149
  };
120
150
  }
121
-
122
151
  //#endregion
123
152
  //#region src/lib/decuplicate.ts
124
153
  function merge_ranges(ranges) {
@@ -141,14 +170,7 @@ function merge_entry_ranges(sheet, entry) {
141
170
  url: entry.url,
142
171
  ranges: [...entry.ranges]
143
172
  };
144
- let seen = new Set(sheet.ranges.map((r) => `${r.start}:${r.end}`));
145
- for (let range of entry.ranges) {
146
- let id = `${range.start}:${range.end}`;
147
- if (!seen.has(id)) {
148
- seen.add(id);
149
- sheet.ranges.push({ ...range });
150
- }
151
- }
173
+ for (let range of entry.ranges) sheet.ranges.push({ ...range });
152
174
  return sheet;
153
175
  }
154
176
  function deduplicate_entries(entries) {
@@ -163,7 +185,6 @@ function deduplicate_entries(entries) {
163
185
  ranges: merge_ranges(ranges)
164
186
  }));
165
187
  }
166
-
167
188
  //#endregion
168
189
  //#region src/lib/ext.ts
169
190
  function ext(url) {
@@ -175,7 +196,6 @@ function ext(url) {
175
196
  return url.slice(ext_index, url.indexOf("/", ext_index) + 1);
176
197
  }
177
198
  }
178
-
179
199
  //#endregion
180
200
  //#region src/lib/html-parser.ts
181
201
  /**
@@ -199,12 +219,11 @@ var DOMParser = class {
199
219
  styles.push({ textContent: text });
200
220
  pos = close + 8;
201
221
  }
202
- return { querySelectorAll(selector) {
222
+ return { querySelectorAll(_selector) {
203
223
  return styles;
204
224
  } };
205
225
  }
206
226
  };
207
-
208
227
  //#endregion
209
228
  //#region src/lib/remap-html.ts
210
229
  function get_dom_parser() {
@@ -217,12 +236,14 @@ function remap_html(html, old_ranges) {
217
236
  let new_ranges = [];
218
237
  let current_offset = 0;
219
238
  let style_elements = doc.querySelectorAll("style");
239
+ let search_from = 0;
220
240
  for (let style_element of Array.from(style_elements)) {
221
241
  let style_content = style_element.textContent;
222
242
  if (!style_content.trim()) continue;
223
243
  combined_css += style_content;
224
- let start_index = html.indexOf(style_content);
244
+ let start_index = html.indexOf(style_content, search_from);
225
245
  let end_index = start_index + style_content.length;
246
+ search_from = end_index;
226
247
  for (let range of old_ranges) if (range.start >= start_index && range.end <= end_index) new_ranges.push({
227
248
  start: current_offset + (range.start - start_index),
228
249
  end: current_offset + (range.end - start_index)
@@ -234,7 +255,6 @@ function remap_html(html, old_ranges) {
234
255
  ranges: new_ranges
235
256
  };
236
257
  }
237
-
238
258
  //#endregion
239
259
  //#region src/lib/filter-entries.ts
240
260
  function is_html(text) {
@@ -279,7 +299,6 @@ function filter_coverage(acc, entry) {
279
299
  }
280
300
  return acc;
281
301
  }
282
-
283
302
  //#endregion
284
303
  //#region src/lib/extend-ranges.ts
285
304
  const AT_SIGN = 64;
@@ -296,12 +315,12 @@ function extend_ranges(coverage) {
296
315
  if (text.charCodeAt(char_position) === AT_SIGN) {
297
316
  range.start = char_position;
298
317
  let next_offset = range.end;
299
- let next_char$1 = text.charAt(next_offset);
300
- while (/\s/.test(next_char$1)) {
318
+ let next_char = text.charAt(next_offset);
319
+ while (/\s/.test(next_char)) {
301
320
  next_offset++;
302
- next_char$1 = text.charAt(next_offset);
321
+ next_char = text.charAt(next_offset);
303
322
  }
304
- if (next_char$1 === "{") range.end = range.end + 1;
323
+ if (next_char === "{") range.end = range.end + 1;
305
324
  break;
306
325
  }
307
326
  }
@@ -317,7 +336,6 @@ function extend_ranges(coverage) {
317
336
  url
318
337
  };
319
338
  }
320
-
321
339
  //#endregion
322
340
  //#region src/lib/index.ts
323
341
  function ratio(fraction, total) {
@@ -360,7 +378,7 @@ function calculate_stylesheet_coverage({ text, url, chunks }) {
360
378
  }
361
379
  function calculate_coverage(coverage) {
362
380
  let total_files_found = coverage.length;
363
- let coverage_per_stylesheet = coverage.reduce((acc, entry) => filter_coverage(acc, entry), []).reduce((entries, entry) => deduplicate_entries(entries.concat(entry)), []).map((coverage$1) => extend_ranges(coverage$1)).map((sheet) => chunkify(sheet)).map((sheet) => prettify(sheet)).map((stylesheet) => calculate_stylesheet_coverage(stylesheet));
381
+ let coverage_per_stylesheet = deduplicate_entries(coverage.reduce((acc, entry) => filter_coverage(acc, entry), [])).map((coverage) => extend_ranges(coverage)).map((sheet) => mark_comments_as_covered(chunkify(sheet))).map((sheet) => prettify(sheet)).map((stylesheet) => calculate_stylesheet_coverage(stylesheet));
364
382
  let { total_lines, total_covered_lines, total_uncovered_lines, total_bytes, total_used_bytes, total_unused_bytes } = coverage_per_stylesheet.reduce((totals, sheet) => {
365
383
  totals.total_lines += sheet.total_lines;
366
384
  totals.total_covered_lines += sheet.covered_lines;
@@ -391,6 +409,5 @@ function calculate_coverage(coverage) {
391
409
  total_stylesheets: coverage_per_stylesheet.length
392
410
  };
393
411
  }
394
-
395
412
  //#endregion
396
- export { calculate_coverage, parse_coverage };
413
+ export { calculate_coverage, parse_coverage };
package/package.json CHANGED
@@ -1,62 +1,67 @@
1
1
  {
2
- "name": "@projectwallace/css-code-coverage",
3
- "version": "0.8.2",
4
- "description": "Generate useful CSS Code Coverage report from browser-reported coverage",
5
- "author": "Bart Veneman <bart@projectwallace.com>",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/projectwallace/css-code-coverage.git"
9
- },
10
- "issues": "https://github.com/projectwallace/css-code-coverage/issues",
11
- "homepage": "https://github.com/projectwallace/css-code-coverage",
12
- "keywords": [
13
- "css",
14
- "code",
15
- "coverage",
16
- "testing",
17
- "used",
18
- "unused",
19
- "dead",
20
- "styles"
21
- ],
22
- "license": "EUPL-1.2",
23
- "engines": {
24
- "node": ">=20"
25
- },
26
- "type": "module",
27
- "files": [
28
- "dist"
29
- ],
30
- "bin": {
31
- "css-coverage": "dist/cli.js"
32
- },
33
- "main": "dist/index.js",
34
- "exports": {
35
- "types": "./dist/index.d.ts",
36
- "default": "./dist/index.js"
37
- },
38
- "types": "dist/index.d.ts",
39
- "scripts": {
40
- "test": "c8 --reporter=text --reporter=lcov playwright test",
41
- "build": "tsdown",
42
- "check": "tsc --noEmit",
43
- "lint": "oxlint --config .oxlintrc.json",
44
- "lint-package": "publint",
45
- "knip": "knip"
46
- },
47
- "devDependencies": {
48
- "@codecov/vite-plugin": "^1.9.1",
49
- "@playwright/test": "^1.56.0",
50
- "@types/node": "^24.9.2",
51
- "c8": "^10.1.3",
52
- "knip": "^5.66.4",
53
- "oxlint": "^1.22.0",
54
- "publint": "^0.3.14",
55
- "tsdown": "^0.15.8",
56
- "typescript": "^5.9.3"
57
- },
58
- "dependencies": {
59
- "@projectwallace/format-css": "^2.1.1",
60
- "valibot": "^1.1.0"
61
- }
2
+ "name": "@projectwallace/css-code-coverage",
3
+ "version": "0.9.1",
4
+ "description": "Generate useful CSS Code Coverage report from browser-reported coverage",
5
+ "keywords": [
6
+ "code",
7
+ "coverage",
8
+ "css",
9
+ "dead",
10
+ "styles",
11
+ "testing",
12
+ "unused",
13
+ "used"
14
+ ],
15
+ "homepage": "https://github.com/projectwallace/css-code-coverage",
16
+ "license": "EUPL-1.2",
17
+ "author": "Bart Veneman <bart@projectwallace.com>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/projectwallace/css-code-coverage.git"
21
+ },
22
+ "bin": {
23
+ "css-coverage": "dist/cli.mjs"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "type": "module",
29
+ "main": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
+ "exports": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "scripts": {
36
+ "test": "c8 --reporter=text --reporter=lcov playwright test",
37
+ "build": "tsdown",
38
+ "check": "tsc --noEmit",
39
+ "lint": "oxlint --config .oxlintrc.json; oxfmt --check",
40
+ "lint-package": "publint",
41
+ "knip": "knip"
42
+ },
43
+ "dependencies": {
44
+ "@projectwallace/css-parser": "^0.13.8",
45
+ "@projectwallace/format-css": "^2.2.6"
46
+ },
47
+ "devDependencies": {
48
+ "@codecov/vite-plugin": "^1.9.1",
49
+ "@playwright/test": "^1.57.0",
50
+ "@projectwallace/preset-oxlint": "^0.0.7",
51
+ "@types/node": "^25.0.3",
52
+ "c8": "^11.0.0",
53
+ "knip": "^5.82.0",
54
+ "oxfmt": "^0.41.0",
55
+ "oxlint": "^1.39.0",
56
+ "publint": "^0.3.16",
57
+ "tsdown": "^0.21.2",
58
+ "typescript": "^5.9.3"
59
+ },
60
+ "overrides": {
61
+ "@projectwallace/css-parser": "$@projectwallace/css-parser"
62
+ },
63
+ "engines": {
64
+ "node": ">=20"
65
+ },
66
+ "issues": "https://github.com/projectwallace/css-code-coverage/issues"
62
67
  }