@canyonjs/report-component 0.0.1-beta.12 → 0.0.1-beta.16

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.
package/dist/index.d.ts CHANGED
@@ -10,12 +10,16 @@ interface FileDataResponse {
10
10
  fileContent: string;
11
11
  fileCodeChange: number[];
12
12
  }
13
+ interface DataSourceItem {
14
+ path: string;
15
+ [key: string]: unknown;
16
+ }
13
17
  interface CanyonReportProps {
14
18
  /** 报告名称 */
15
19
  name: string;
16
20
  /** 当前选中的文件 */
17
21
  value: string;
18
- dataSource: any[];
22
+ dataSource: DataSourceItem[];
19
23
  onSelect: (val: string) => Promise<FileDataResponse>;
20
24
  }
21
25
  //#endregion
package/dist/index.js CHANGED
@@ -2,565 +2,13 @@ import { ConfigProvider, Divider, Input, Progress, Segmented, Space, Spin, Switc
2
2
  import { Suspense, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { genSummaryTreeItem } from "canyon-data";
5
+ import { renderToStaticMarkup } from "react-dom/server";
5
6
  import Highlighter from "react-highlight-words";
6
7
  import { FileOutlined, FolderFilled } from "@ant-design/icons";
7
8
 
8
- //#region rolldown:runtime
9
- var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
10
-
11
- //#endregion
12
- //#region ../../node_modules/.pnpm/istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage/lib/percent.js
13
- var require_percent = /* @__PURE__ */ __commonJSMin(((exports, module) => {
14
- module.exports = function percent$2(covered, total) {
15
- let tmp;
16
- if (total > 0) {
17
- tmp = 1e3 * 100 * covered / total;
18
- return Math.floor(tmp / 10) / 100;
19
- } else return 100;
20
- };
21
- }));
22
-
23
- //#endregion
24
- //#region ../../node_modules/.pnpm/istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage/lib/data-properties.js
25
- var require_data_properties = /* @__PURE__ */ __commonJSMin(((exports, module) => {
26
- module.exports = function dataProperties$2(klass, properties) {
27
- properties.forEach((p) => {
28
- Object.defineProperty(klass.prototype, p, {
29
- enumerable: true,
30
- get() {
31
- return this.data[p];
32
- }
33
- });
34
- });
35
- };
36
- }));
37
-
38
- //#endregion
39
- //#region ../../node_modules/.pnpm/istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage/lib/coverage-summary.js
40
- var require_coverage_summary = /* @__PURE__ */ __commonJSMin(((exports, module) => {
41
- const percent$1 = require_percent();
42
- const dataProperties$1 = require_data_properties();
43
- function blankSummary() {
44
- const empty = () => ({
45
- total: 0,
46
- covered: 0,
47
- skipped: 0,
48
- pct: "Unknown"
49
- });
50
- return {
51
- lines: empty(),
52
- statements: empty(),
53
- functions: empty(),
54
- branches: empty(),
55
- branchesTrue: empty()
56
- };
57
- }
58
- function assertValidSummary(obj) {
59
- if (!(obj && obj.lines && obj.statements && obj.functions && obj.branches)) throw new Error("Invalid summary coverage object, missing keys, found:" + Object.keys(obj).join(","));
60
- }
61
- /**
62
- * CoverageSummary provides a summary of code coverage . It exposes 4 properties,
63
- * `lines`, `statements`, `branches`, and `functions`. Each of these properties
64
- * is an object that has 4 keys `total`, `covered`, `skipped` and `pct`.
65
- * `pct` is a percentage number (0-100).
66
- */
67
- var CoverageSummary$3 = class CoverageSummary$3 {
68
- /**
69
- * @constructor
70
- * @param {Object|CoverageSummary} [obj=undefined] an optional data object or
71
- * another coverage summary to initialize this object with.
72
- */
73
- constructor(obj) {
74
- if (!obj) this.data = blankSummary();
75
- else if (obj instanceof CoverageSummary$3) this.data = obj.data;
76
- else this.data = obj;
77
- assertValidSummary(this.data);
78
- }
79
- /**
80
- * merges a second summary coverage object into this one
81
- * @param {CoverageSummary} obj - another coverage summary object
82
- */
83
- merge(obj) {
84
- [
85
- "lines",
86
- "statements",
87
- "branches",
88
- "functions",
89
- "branchesTrue"
90
- ].forEach((key) => {
91
- if (obj[key]) {
92
- this[key].total += obj[key].total;
93
- this[key].covered += obj[key].covered;
94
- this[key].skipped += obj[key].skipped;
95
- this[key].pct = percent$1(this[key].covered, this[key].total);
96
- }
97
- });
98
- return this;
99
- }
100
- /**
101
- * returns a POJO that is JSON serializable. May be used to get the raw
102
- * summary object.
103
- */
104
- toJSON() {
105
- return this.data;
106
- }
107
- /**
108
- * return true if summary has no lines of code
109
- */
110
- isEmpty() {
111
- return this.lines.total === 0;
112
- }
113
- };
114
- dataProperties$1(CoverageSummary$3, [
115
- "lines",
116
- "statements",
117
- "functions",
118
- "branches",
119
- "branchesTrue"
120
- ]);
121
- module.exports = { CoverageSummary: CoverageSummary$3 };
122
- }));
123
-
124
- //#endregion
125
- //#region ../../node_modules/.pnpm/istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage/lib/file-coverage.js
126
- var require_file_coverage = /* @__PURE__ */ __commonJSMin(((exports, module) => {
127
- const percent = require_percent();
128
- const dataProperties = require_data_properties();
129
- const { CoverageSummary: CoverageSummary$2 } = require_coverage_summary();
130
- function emptyCoverage(filePath, reportLogic) {
131
- const cov = {
132
- path: filePath,
133
- statementMap: {},
134
- fnMap: {},
135
- branchMap: {},
136
- s: {},
137
- f: {},
138
- b: {}
139
- };
140
- if (reportLogic) cov.bT = {};
141
- return cov;
142
- }
143
- function assertValidObject(obj) {
144
- if (!(obj && obj.path && obj.statementMap && obj.fnMap && obj.branchMap && obj.s && obj.f && obj.b)) throw new Error("Invalid file coverage object, missing keys, found:" + Object.keys(obj).join(","));
145
- }
146
- const keyFromLoc = ({ start, end }) => `${start.line}|${start.column}|${end.line}|${end.column}`;
147
- const isObj = (o) => !!o && typeof o === "object";
148
- const isLineCol = (o) => isObj(o) && typeof o.line === "number" && typeof o.column === "number";
149
- const isLoc = (o) => isObj(o) && isLineCol(o.start) && isLineCol(o.end);
150
- const getLoc = (o) => isLoc(o) ? o : isLoc(o.loc) ? o.loc : null;
151
- const findNearestContainer = (item, map) => {
152
- const itemLoc = getLoc(item);
153
- if (!itemLoc) return null;
154
- let nearestContainingItem = null;
155
- let containerDistance = null;
156
- let containerKey = null;
157
- for (const [i, mapItem] of Object.entries(map)) {
158
- const mapLoc = getLoc(mapItem);
159
- if (!mapLoc) continue;
160
- const distance = [
161
- itemLoc.start.line - mapLoc.start.line,
162
- itemLoc.start.column - mapLoc.start.column,
163
- mapLoc.end.line - itemLoc.end.line,
164
- mapLoc.end.column - itemLoc.end.column
165
- ];
166
- if (distance[0] < 0 || distance[2] < 0 || distance[0] === 0 && distance[1] < 0 || distance[2] === 0 && distance[3] < 0) continue;
167
- if (nearestContainingItem === null) {
168
- containerDistance = distance;
169
- nearestContainingItem = mapItem;
170
- containerKey = i;
171
- continue;
172
- }
173
- const closerBefore = distance[0] < containerDistance[0] || distance[0] === 0 && distance[1] < containerDistance[1];
174
- const closerAfter = distance[2] < containerDistance[2] || distance[2] === 0 && distance[3] < containerDistance[3];
175
- if (closerBefore || closerAfter) {
176
- containerDistance = distance;
177
- nearestContainingItem = mapItem;
178
- containerKey = i;
179
- }
180
- }
181
- return containerKey;
182
- };
183
- const addHits = (aHits, bHits) => {
184
- if (typeof aHits === "number" && typeof bHits === "number") return aHits + bHits;
185
- else if (Array.isArray(aHits) && Array.isArray(bHits)) return aHits.map((a, i) => (a || 0) + (bHits[i] || 0));
186
- return null;
187
- };
188
- const addNearestContainerHits = (item, itemHits, map, mapHits) => {
189
- const container = findNearestContainer(item, map);
190
- if (container) return addHits(itemHits, mapHits[container]);
191
- else return itemHits;
192
- };
193
- const mergeProp = (aHits, aMap, bHits, bMap, itemKey = keyFromLoc) => {
194
- const aItems = {};
195
- for (const [key, itemHits] of Object.entries(aHits)) {
196
- const item = aMap[key];
197
- aItems[itemKey(item)] = [itemHits, item];
198
- }
199
- const bItems = {};
200
- for (const [key, itemHits] of Object.entries(bHits)) {
201
- const item = bMap[key];
202
- bItems[itemKey(item)] = [itemHits, item];
203
- }
204
- const mergedItems = {};
205
- for (const [key, aValue] of Object.entries(aItems)) {
206
- let aItemHits = aValue[0];
207
- const aItem = aValue[1];
208
- const bValue = bItems[key];
209
- if (!bValue) aItemHits = addNearestContainerHits(aItem, aItemHits, bMap, bHits);
210
- else aItemHits = addHits(aItemHits, bValue[0]);
211
- mergedItems[key] = [aItemHits, aItem];
212
- }
213
- for (const [key, bValue] of Object.entries(bItems)) {
214
- let bItemHits = bValue[0];
215
- const bItem = bValue[1];
216
- if (mergedItems[key]) continue;
217
- bItemHits = addNearestContainerHits(bItem, bItemHits, aMap, aHits);
218
- mergedItems[key] = [bItemHits, bItem];
219
- }
220
- const hits = {};
221
- const map = {};
222
- Object.values(mergedItems).forEach(([itemHits, item], i) => {
223
- hits[i] = itemHits;
224
- map[i] = item;
225
- });
226
- return [hits, map];
227
- };
228
- /**
229
- * provides a read-only view of coverage for a single file.
230
- * The deep structure of this object is documented elsewhere. It has the following
231
- * properties:
232
- *
233
- * * `path` - the file path for which coverage is being tracked
234
- * * `statementMap` - map of statement locations keyed by statement index
235
- * * `fnMap` - map of function metadata keyed by function index
236
- * * `branchMap` - map of branch metadata keyed by branch index
237
- * * `s` - hit counts for statements
238
- * * `f` - hit count for functions
239
- * * `b` - hit count for branches
240
- */
241
- var FileCoverage$2 = class FileCoverage$2 {
242
- /**
243
- * @constructor
244
- * @param {Object|FileCoverage|String} pathOrObj is a string that initializes
245
- * and empty coverage object with the specified file path or a data object that
246
- * has all the required properties for a file coverage object.
247
- */
248
- constructor(pathOrObj, reportLogic = false) {
249
- if (!pathOrObj) throw new Error("Coverage must be initialized with a path or an object");
250
- if (typeof pathOrObj === "string") this.data = emptyCoverage(pathOrObj, reportLogic);
251
- else if (pathOrObj instanceof FileCoverage$2) this.data = pathOrObj.data;
252
- else if (typeof pathOrObj === "object") this.data = pathOrObj;
253
- else throw new Error("Invalid argument to coverage constructor");
254
- assertValidObject(this.data);
255
- }
256
- /**
257
- * returns computed line coverage from statement coverage.
258
- * This is a map of hits keyed by line number in the source.
259
- */
260
- getLineCoverage() {
261
- const statementMap = this.data.statementMap;
262
- const statements = this.data.s;
263
- const lineMap = Object.create(null);
264
- Object.entries(statements).forEach(([st, count]) => {
265
- /* istanbul ignore if: is this even possible? */
266
- if (!statementMap[st]) return;
267
- const { line } = statementMap[st].start;
268
- const prevVal = lineMap[line];
269
- if (prevVal === void 0 || prevVal < count) lineMap[line] = count;
270
- });
271
- return lineMap;
272
- }
273
- /**
274
- * returns an array of uncovered line numbers.
275
- * @returns {Array} an array of line numbers for which no hits have been
276
- * collected.
277
- */
278
- getUncoveredLines() {
279
- const lc = this.getLineCoverage();
280
- const ret = [];
281
- Object.entries(lc).forEach(([l, hits]) => {
282
- if (hits === 0) ret.push(l);
283
- });
284
- return ret;
285
- }
286
- /**
287
- * returns a map of branch coverage by source line number.
288
- * @returns {Object} an object keyed by line number. Each object
289
- * has a `covered`, `total` and `coverage` (percentage) property.
290
- */
291
- getBranchCoverageByLine() {
292
- const branchMap = this.branchMap;
293
- const branches = this.b;
294
- const ret = {};
295
- Object.entries(branchMap).forEach(([k, map]) => {
296
- const line = map.line || map.loc.start.line;
297
- const branchData = branches[k];
298
- ret[line] = ret[line] || [];
299
- ret[line].push(...branchData);
300
- });
301
- Object.entries(ret).forEach(([k, dataArray]) => {
302
- const covered = dataArray.filter((item) => item > 0);
303
- const coverage = covered.length / dataArray.length * 100;
304
- ret[k] = {
305
- covered: covered.length,
306
- total: dataArray.length,
307
- coverage
308
- };
309
- });
310
- return ret;
311
- }
312
- /**
313
- * return a JSON-serializable POJO for this file coverage object
314
- */
315
- toJSON() {
316
- return this.data;
317
- }
318
- /**
319
- * merges a second coverage object into this one, updating hit counts
320
- * @param {FileCoverage} other - the coverage object to be merged into this one.
321
- * Note that the other object should have the same structure as this one (same file).
322
- */
323
- merge(other) {
324
- if (other.all === true) return;
325
- if (this.all === true) {
326
- this.data = other.data;
327
- return;
328
- }
329
- let [hits, map] = mergeProp(this.s, this.statementMap, other.s, other.statementMap);
330
- this.data.s = hits;
331
- this.data.statementMap = map;
332
- const keyFromLocProp = (x) => keyFromLoc(x.loc);
333
- const keyFromLocationsProp = (x) => keyFromLoc(x.locations[0]);
334
- [hits, map] = mergeProp(this.f, this.fnMap, other.f, other.fnMap, keyFromLocProp);
335
- this.data.f = hits;
336
- this.data.fnMap = map;
337
- [hits, map] = mergeProp(this.b, this.branchMap, other.b, other.branchMap, keyFromLocationsProp);
338
- this.data.b = hits;
339
- this.data.branchMap = map;
340
- if (this.bT && other.bT) {
341
- [hits, map] = mergeProp(this.bT, this.branchMap, other.bT, other.branchMap, keyFromLocationsProp);
342
- this.data.bT = hits;
343
- }
344
- }
345
- computeSimpleTotals(property) {
346
- let stats = this[property];
347
- if (typeof stats === "function") stats = stats.call(this);
348
- const ret = {
349
- total: Object.keys(stats).length,
350
- covered: Object.values(stats).filter((v) => !!v).length,
351
- skipped: 0
352
- };
353
- ret.pct = percent(ret.covered, ret.total);
354
- return ret;
355
- }
356
- computeBranchTotals(property) {
357
- const stats = this[property];
358
- const ret = {
359
- total: 0,
360
- covered: 0,
361
- skipped: 0
362
- };
363
- Object.values(stats).forEach((branches) => {
364
- ret.covered += branches.filter((hits) => hits > 0).length;
365
- ret.total += branches.length;
366
- });
367
- ret.pct = percent(ret.covered, ret.total);
368
- return ret;
369
- }
370
- /**
371
- * resets hit counts for all statements, functions and branches
372
- * in this coverage object resulting in zero coverage.
373
- */
374
- resetHits() {
375
- const statements = this.s;
376
- const functions = this.f;
377
- const branches = this.b;
378
- const branchesTrue = this.bT;
379
- Object.keys(statements).forEach((s) => {
380
- statements[s] = 0;
381
- });
382
- Object.keys(functions).forEach((f) => {
383
- functions[f] = 0;
384
- });
385
- Object.keys(branches).forEach((b) => {
386
- branches[b].fill(0);
387
- });
388
- if (branchesTrue) Object.keys(branchesTrue).forEach((bT) => {
389
- branchesTrue[bT].fill(0);
390
- });
391
- }
392
- /**
393
- * returns a CoverageSummary for this file coverage object
394
- * @returns {CoverageSummary}
395
- */
396
- toSummary() {
397
- const ret = {};
398
- ret.lines = this.computeSimpleTotals("getLineCoverage");
399
- ret.functions = this.computeSimpleTotals("f", "fnMap");
400
- ret.statements = this.computeSimpleTotals("s", "statementMap");
401
- ret.branches = this.computeBranchTotals("b");
402
- if (this.bT) ret.branchesTrue = this.computeBranchTotals("bT");
403
- return new CoverageSummary$2(ret);
404
- }
405
- };
406
- dataProperties(FileCoverage$2, [
407
- "path",
408
- "statementMap",
409
- "fnMap",
410
- "branchMap",
411
- "s",
412
- "f",
413
- "b",
414
- "bT",
415
- "all"
416
- ]);
417
- module.exports = {
418
- FileCoverage: FileCoverage$2,
419
- findNearestContainer,
420
- addHits,
421
- addNearestContainerHits
422
- };
423
- }));
424
-
425
- //#endregion
426
- //#region ../../node_modules/.pnpm/istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage/lib/coverage-map.js
427
- var require_coverage_map = /* @__PURE__ */ __commonJSMin(((exports, module) => {
428
- const { FileCoverage: FileCoverage$1 } = require_file_coverage();
429
- const { CoverageSummary: CoverageSummary$1 } = require_coverage_summary();
430
- function maybeConstruct(obj, klass) {
431
- if (obj instanceof klass) return obj;
432
- return new klass(obj);
433
- }
434
- function loadMap(source) {
435
- const data = Object.create(null);
436
- if (!source) return data;
437
- Object.entries(source).forEach(([k, cov]) => {
438
- data[k] = maybeConstruct(cov, FileCoverage$1);
439
- });
440
- return data;
441
- }
442
- /** CoverageMap is a map of `FileCoverage` objects keyed by file paths. */
443
- var CoverageMap$1 = class CoverageMap$1 {
444
- /**
445
- * @constructor
446
- * @param {Object} [obj=undefined] obj A coverage map from which to initialize this
447
- * map's contents. This can be the raw global coverage object.
448
- */
449
- constructor(obj) {
450
- if (obj instanceof CoverageMap$1) this.data = obj.data;
451
- else this.data = loadMap(obj);
452
- }
453
- /**
454
- * merges a second coverage map into this one
455
- * @param {CoverageMap} obj - a CoverageMap or its raw data. Coverage is merged
456
- * correctly for the same files and additional file coverage keys are created
457
- * as needed.
458
- */
459
- merge(obj) {
460
- const other = maybeConstruct(obj, CoverageMap$1);
461
- Object.values(other.data).forEach((fc) => {
462
- this.addFileCoverage(fc);
463
- });
464
- }
465
- /**
466
- * filter the coveragemap based on the callback provided
467
- * @param {Function (filename)} callback - Returns true if the path
468
- * should be included in the coveragemap. False if it should be
469
- * removed.
470
- */
471
- filter(callback) {
472
- Object.keys(this.data).forEach((k) => {
473
- if (!callback(k)) delete this.data[k];
474
- });
475
- }
476
- /**
477
- * returns a JSON-serializable POJO for this coverage map
478
- * @returns {Object}
479
- */
480
- toJSON() {
481
- return this.data;
482
- }
483
- /**
484
- * returns an array for file paths for which this map has coverage
485
- * @returns {Array{string}} - array of files
486
- */
487
- files() {
488
- return Object.keys(this.data);
489
- }
490
- /**
491
- * returns the file coverage for the specified file.
492
- * @param {String} file
493
- * @returns {FileCoverage}
494
- */
495
- fileCoverageFor(file) {
496
- const fc = this.data[file];
497
- if (!fc) throw new Error(`No file coverage available for: ${file}`);
498
- return fc;
499
- }
500
- /**
501
- * adds a file coverage object to this map. If the path for the object,
502
- * already exists in the map, it is merged with the existing coverage
503
- * otherwise a new key is added to the map.
504
- * @param {FileCoverage} fc the file coverage to add
505
- */
506
- addFileCoverage(fc) {
507
- const cov = new FileCoverage$1(fc);
508
- const { path } = cov;
509
- if (this.data[path]) this.data[path].merge(cov);
510
- else this.data[path] = cov;
511
- }
512
- /**
513
- * returns the coverage summary for all the file coverage objects in this map.
514
- * @returns {CoverageSummary}
515
- */
516
- getCoverageSummary() {
517
- const ret = new CoverageSummary$1();
518
- Object.values(this.data).forEach((fc) => {
519
- ret.merge(fc.toSummary());
520
- });
521
- return ret;
522
- }
523
- };
524
- module.exports = { CoverageMap: CoverageMap$1 };
525
- }));
526
-
527
- //#endregion
528
- //#region ../../node_modules/.pnpm/istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage/index.js
529
- var require_istanbul_lib_coverage = /* @__PURE__ */ __commonJSMin(((exports, module) => {
530
- /**
531
- * istanbul-lib-coverage exports an API that allows you to create and manipulate
532
- * file coverage, coverage maps (a set of file coverage objects) and summary
533
- * coverage objects. File coverage for the same file can be merged as can
534
- * entire coverage maps.
535
- *
536
- * @module Exports
537
- */
538
- const { FileCoverage } = require_file_coverage();
539
- const { CoverageMap } = require_coverage_map();
540
- const { CoverageSummary } = require_coverage_summary();
541
- module.exports = {
542
- createCoverageSummary(obj) {
543
- if (obj && obj instanceof CoverageSummary) return obj;
544
- return new CoverageSummary(obj);
545
- },
546
- createCoverageMap(obj) {
547
- if (obj && obj instanceof CoverageMap) return obj;
548
- return new CoverageMap(obj);
549
- },
550
- createFileCoverage(obj) {
551
- if (obj && obj instanceof FileCoverage) return obj;
552
- return new FileCoverage(obj);
553
- }
554
- };
555
- /** classes exported for reuse */
556
- module.exports.classes = { FileCoverage };
557
- }));
558
-
559
- //#endregion
560
9
  //#region src/components/RIf.tsx
561
- var import_istanbul_lib_coverage = require_istanbul_lib_coverage();
562
10
  const RIf = ({ condition, children }) => {
563
- return /* @__PURE__ */ jsx(Fragment, { children: condition ? /* @__PURE__ */ jsx(Fragment, { children }) : null });
11
+ return /* @__PURE__ */ jsx(Fragment, { children: condition ? children : null });
564
12
  };
565
13
  var RIf_default = RIf;
566
14
 
@@ -598,28 +46,312 @@ const generateCoreDataForEachComponent = ({ dataSource, filenameKeywords, value,
598
46
  };
599
47
  };
600
48
 
49
+ //#endregion
50
+ //#region src/helpers/annotate.ts
51
+ function annotateStatements(fileCoverage) {
52
+ const annotateStatementsList = [];
53
+ const statementStats = fileCoverage.s;
54
+ const statementMeta = fileCoverage.statementMap;
55
+ Object.entries(statementStats).forEach(([stName, count]) => {
56
+ const meta = statementMeta[stName];
57
+ const type = count > 0 ? "yes" : "no";
58
+ const startCol = meta.start.column;
59
+ const endCol = meta.end.column + 1;
60
+ const startLine = meta.start.line;
61
+ const endLine = meta.end.line;
62
+ if (type === "no") annotateStatementsList.push({
63
+ startLine,
64
+ endLine,
65
+ startCol: startCol + 1,
66
+ endCol: endCol + 1,
67
+ type: "S"
68
+ });
69
+ });
70
+ return annotateStatementsList;
71
+ }
72
+ function annotateFunctions(fileCoverage, structuredText) {
73
+ const fnStats = fileCoverage.f;
74
+ const fnMeta = fileCoverage.fnMap;
75
+ if (!fnStats) return [];
76
+ const list = [];
77
+ Object.entries(fnStats).forEach(([fName, count]) => {
78
+ const meta = fnMeta[fName];
79
+ const type = count > 0 ? "yes" : "no";
80
+ const decl = meta.decl || meta.loc;
81
+ const startCol = decl.start.column;
82
+ let endCol = decl.end.column + 1;
83
+ const startLine = decl.start.line;
84
+ const endLine = decl.end.line;
85
+ if (type === "no") {
86
+ if (endLine !== startLine) endCol = structuredText[startLine - 1].length;
87
+ list.push({
88
+ startLine,
89
+ endLine,
90
+ startCol: startCol + 1,
91
+ endCol: endCol + 1,
92
+ type: "F"
93
+ });
94
+ }
95
+ });
96
+ return list;
97
+ }
98
+ function annotateBranches(fileCoverage, structuredText) {
99
+ const branchStats = fileCoverage.b;
100
+ const branchMeta = fileCoverage.branchMap;
101
+ if (!branchStats) return [];
102
+ const arr = [];
103
+ Object.entries(branchStats).forEach(([branchName, branchArray]) => {
104
+ const sumCount = branchArray.reduce((p, n) => p + n, 0);
105
+ const metaArray = branchMeta[branchName].locations;
106
+ let i;
107
+ let count;
108
+ let meta;
109
+ let startCol;
110
+ let endCol;
111
+ let startLine;
112
+ let endLine;
113
+ if (sumCount > 0 || sumCount === 0 && branchArray.length === 1) {
114
+ if (branchMeta[branchName].type === "if" && branchArray.length === 2 && metaArray.length === 1 && branchArray[1] === 0) metaArray[1] = {
115
+ start: {},
116
+ end: {}
117
+ };
118
+ for (i = 0; i < branchArray.length && i < metaArray.length; i += 1) {
119
+ count = branchArray[i];
120
+ meta = metaArray[i];
121
+ startCol = meta.start.column;
122
+ endCol = meta.end.column + 1;
123
+ startLine = meta.start.line;
124
+ endLine = meta.end.line;
125
+ if (count === 0 && startLine === void 0 && branchMeta[branchName].type === "if") {
126
+ const prevMeta = metaArray[i - 1];
127
+ startCol = prevMeta.start.column;
128
+ endCol = prevMeta.end.column + 1;
129
+ startLine = prevMeta.start.line;
130
+ endLine = prevMeta.end.line;
131
+ }
132
+ if (count === 0 && structuredText[startLine]) {
133
+ if (endLine !== startLine) {}
134
+ structuredText[startLine].text;
135
+ if (branchMeta[branchName].type === "if") arr.push({
136
+ startLine,
137
+ endLine,
138
+ startCol: startCol + 1,
139
+ endCol: endCol + 1,
140
+ type: i === 0 ? "I" : "E",
141
+ skip: meta.skip
142
+ });
143
+ else arr.push({
144
+ startLine,
145
+ endLine,
146
+ startCol: startCol + 1,
147
+ endCol: endCol + 1,
148
+ type: "B",
149
+ skip: meta.skip
150
+ });
151
+ }
152
+ }
153
+ }
154
+ });
155
+ return arr;
156
+ }
157
+
158
+ //#endregion
159
+ //#region src/helpers/coreFn.ts
160
+ function coreFn(fileCoverage, fileDetail) {
161
+ const nullData = {
162
+ times: [],
163
+ rows: [],
164
+ maxWidth: 0,
165
+ lines: []
166
+ };
167
+ if (!fileCoverage.s) return nullData;
168
+ const content = fileDetail;
169
+ const rows = [""];
170
+ let index = 0;
171
+ for (let i = 0; i < content.length; i++) if (content[i] === "\n") {
172
+ index += 1;
173
+ rows.push("");
174
+ } else rows[index] += content[i];
175
+ const maxWidth = JSON.parse(JSON.stringify(rows)).sort((a, b) => -(a.length - b.length))[0].length;
176
+ function getLineCoverage(data) {
177
+ const statementMap = data.statementMap;
178
+ const statements = data.s;
179
+ const lineMap = Object.create(null);
180
+ Object.entries(statements).forEach(([st, count]) => {
181
+ if (!statementMap[st]) return;
182
+ const { line } = statementMap[st].start;
183
+ const prevVal = lineMap[line];
184
+ if (prevVal === void 0 || prevVal < count) lineMap[line] = count;
185
+ });
186
+ return lineMap;
187
+ }
188
+ const lineStats = getLineCoverage(fileCoverage);
189
+ if (!lineStats) return nullData;
190
+ const numberOfRows = [];
191
+ Object.entries(lineStats).forEach(([lineNumber, count]) => {
192
+ numberOfRows.push({
193
+ lineNumber,
194
+ count
195
+ });
196
+ });
197
+ const lines = [];
198
+ for (let i = 0; i < rows.length; i++) if (numberOfRows.find((n) => Number(n.lineNumber) === i + 1)) lines.push({ executionNumber: numberOfRows.find((n) => Number(n.lineNumber) === i + 1).count });
199
+ else lines.push({ executionNumber: -1 });
200
+ return {
201
+ times: numberOfRows,
202
+ rows,
203
+ lines,
204
+ maxWidth
205
+ };
206
+ }
207
+
208
+ //#endregion
209
+ //#region src/widgets/lineNumbers.tsx
210
+ function genBgColor(hit) {
211
+ if (hit > 0) return "rgb(230, 245, 208)";
212
+ else if (hit === 0) return "#f3aeac";
213
+ else return "rgb(234, 234, 234)";
214
+ }
215
+ const LineNumber = ({ lineNumber }) => {
216
+ return /* @__PURE__ */ jsx("span", {
217
+ className: "line-number",
218
+ children: lineNumber
219
+ });
220
+ };
221
+ const LineChange = ({ hasChange }) => {
222
+ return /* @__PURE__ */ jsx("span", {
223
+ className: "line-change",
224
+ children: hasChange ? "+" : ""
225
+ });
226
+ };
227
+ const LineCoverage = ({ hit, width }) => {
228
+ return /* @__PURE__ */ jsx("span", {
229
+ className: "line-coverage",
230
+ style: {
231
+ background: genBgColor(hit),
232
+ width: `${width}px`
233
+ },
234
+ children: hit > 0 ? `${hit}x` : ""
235
+ });
236
+ };
237
+ const LineNumberWrapper = ({ lineNumber, line, maxHitWidth }) => {
238
+ return /* @__PURE__ */ jsxs("div", {
239
+ className: "line-number-wrapper",
240
+ children: [
241
+ /* @__PURE__ */ jsx(LineNumber, { lineNumber }),
242
+ /* @__PURE__ */ jsx(LineChange, { hasChange: line.change }),
243
+ /* @__PURE__ */ jsx(LineCoverage, {
244
+ hit: line.hit,
245
+ width: maxHitWidth
246
+ })
247
+ ]
248
+ });
249
+ };
250
+ function lineNumbers(lineNumber, linesState, _addLines) {
251
+ return renderToStaticMarkup(/* @__PURE__ */ jsx(LineNumberWrapper, {
252
+ lineNumber,
253
+ line: linesState.find((line) => line.lineNumber === lineNumber) || {
254
+ change: false,
255
+ hit: 0,
256
+ lineNumber
257
+ },
258
+ maxHitWidth: (Math.max(...linesState.map((line) => line.hit)).toString().length + 2) * 7.2
259
+ }));
260
+ }
261
+
601
262
  //#endregion
602
263
  //#region src/widgets/CoverageDetail.tsx
603
- function CanyonReport$1({ fileContent, fileCoverage, fileCodeChange }) {
604
- console.log(fileCodeChange, "fileCodeChange");
264
+ const CoverageDetail = ({ source, coverage, diff }) => {
265
+ const { lines } = coreFn(coverage, source);
266
+ const addLines = diff.additions || [];
267
+ const linesState = (() => {
268
+ return lines.map((line, index) => {
269
+ return {
270
+ lineNumber: index + 1,
271
+ change: addLines.includes(index + 1),
272
+ hit: line.executionNumber
273
+ };
274
+ });
275
+ })();
276
+ const lineNumbersMinChars = (() => {
277
+ return Math.max(...linesState.map((line) => line.hit)).toString().length + 8;
278
+ })();
605
279
  const ref = useRef(null);
606
280
  useEffect(() => {
607
281
  if (ref.current) {
608
282
  const dom = ref.current;
609
283
  const options = {
610
- value: fileContent,
284
+ value: source,
611
285
  language: "javascript",
612
- fontFamily: "IBMPlexMono"
286
+ fontFamily: "IBMPlexMono",
287
+ lineHeight: 18,
288
+ lineNumbers: (lineNumber) => {
289
+ return lineNumbers(lineNumber, linesState);
290
+ },
291
+ lineNumbersMinChars,
292
+ readOnly: true,
293
+ folding: false,
294
+ minimap: { enabled: false },
295
+ scrollBeyondLastLine: false,
296
+ showUnused: false,
297
+ fontSize: 12,
298
+ contextmenu: false,
299
+ automaticLayout: true
613
300
  };
614
- if (window.monaco?.editor && dom) window.monaco.editor.create(dom, options);
301
+ if (window.monaco?.editor && dom) {
302
+ const editor = window.monaco.editor.create(dom, options);
303
+ const decorations = (() => {
304
+ const all = [];
305
+ const annotateStatementsList = annotateStatements(coverage);
306
+ all.push(...annotateStatementsList);
307
+ const annotateFunctionsList = annotateFunctions(coverage, source);
308
+ all.push(...annotateFunctionsList);
309
+ const annotateBranchesList = annotateBranches(coverage, source);
310
+ all.push(...annotateBranchesList);
311
+ const arr = [];
312
+ for (let i = 0; i < all.length; i++) {
313
+ const { startLine, startCol, endLine, endCol } = all[i];
314
+ if (all[i].type === "S" || all[i].type === "F") arr.push({
315
+ range: new window.monaco.Range(startLine, startCol, endLine, endCol),
316
+ options: {
317
+ isWholeLine: false,
318
+ inlineClassName: "content-class-no-found"
319
+ }
320
+ });
321
+ else if (all[i].type === "B") arr.push({
322
+ range: new window.monaco.Range(startLine, startCol, endLine, endCol),
323
+ options: {
324
+ isWholeLine: false,
325
+ inlineClassName: "content-class-no-found-branch"
326
+ }
327
+ });
328
+ else if (all[i].type === "I") arr.push({
329
+ range: new window.monaco.Range(startLine, startCol, startLine, startCol),
330
+ options: {
331
+ beforeContentClassName: "insert-i-decoration",
332
+ stickiness: window.monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
333
+ }
334
+ });
335
+ else if (all[i].type === "E") arr.push({
336
+ range: new window.monaco.Range(startLine, startCol, startLine, startCol),
337
+ options: {
338
+ beforeContentClassName: "insert-e-decoration",
339
+ stickiness: window.monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
340
+ }
341
+ });
342
+ }
343
+ return arr;
344
+ })();
345
+ if (editor) editor?.createDecorationsCollection?.(decorations);
346
+ }
615
347
  }
616
- }, []);
348
+ }, [source]);
617
349
  return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", {
618
350
  ref,
619
351
  style: { height: "calc(100vh - 150px)" }
620
352
  }) });
621
- }
622
- var CoverageDetail_default = CanyonReport$1;
353
+ };
354
+ var CoverageDetail_default = CoverageDetail;
623
355
 
624
356
  //#endregion
625
357
  //#region src/helpers/color.ts
@@ -643,24 +375,30 @@ const SummaryNav = ({ value, onClick, reportName }) => {
643
375
  fontSize: "16px",
644
376
  fontWeight: "bold"
645
377
  },
646
- children: `${reportName}/${value}`.split("/").map((item, index) => {
378
+ children: `${reportName}/${value}`.split("/").map((item, index, array) => {
379
+ const pathKey = `${reportName}-${array.slice(0, index + 1).join("/")}`;
647
380
  return /* @__PURE__ */ jsxs("div", {
648
381
  style: {
649
382
  display: "flex",
650
383
  gap: "6px"
651
384
  },
652
- children: [/* @__PURE__ */ jsx("a", {
385
+ children: [/* @__PURE__ */ jsx("button", {
386
+ type: "button",
653
387
  style: {
654
388
  color: token.colorPrimary,
655
389
  cursor: "pointer",
656
- textDecoration: "none"
390
+ textDecoration: "none",
391
+ background: "none",
392
+ border: "none",
393
+ padding: 0,
394
+ font: "inherit"
657
395
  },
658
396
  onClick: () => {
659
397
  onClick(value.split("/").slice(0, index).join("/"));
660
398
  },
661
399
  children: item
662
- }, index), index === value.split("/").length || !value ? null : /* @__PURE__ */ jsx("span", { children: "/" })]
663
- }, index);
400
+ }), index === value.split("/").length || !value ? null : /* @__PURE__ */ jsx("span", { children: "/" })]
401
+ }, pathKey);
664
402
  })
665
403
  });
666
404
  };
@@ -767,11 +505,19 @@ const SummaryList = ({ dataSource, onSelect, filenameKeywords, style, onlyChange
767
505
  key: "path",
768
506
  dataIndex: "path",
769
507
  render(text) {
770
- return /* @__PURE__ */ jsx("a", {
508
+ return /* @__PURE__ */ jsx("button", {
509
+ type: "button",
771
510
  style: {
772
511
  width: "420px",
773
512
  display: "block",
774
- overflowWrap: "break-word"
513
+ overflowWrap: "break-word",
514
+ background: "none",
515
+ border: "none",
516
+ padding: 0,
517
+ textAlign: "left",
518
+ cursor: "pointer",
519
+ color: "inherit",
520
+ font: "inherit"
775
521
  },
776
522
  onClick: () => {
777
523
  onSelect(text);
@@ -889,10 +635,18 @@ const SummaryTree = ({ dataSource, onSelect, style, onlyChange }) => {
889
635
  key: "path",
890
636
  dataIndex: "path",
891
637
  render(text) {
892
- return /* @__PURE__ */ jsxs("a", {
638
+ return /* @__PURE__ */ jsxs("button", {
639
+ type: "button",
893
640
  style: {
894
641
  display: "flex",
895
- gap: "6px"
642
+ gap: "6px",
643
+ background: "none",
644
+ border: "none",
645
+ padding: 0,
646
+ cursor: "pointer",
647
+ color: "inherit",
648
+ font: "inherit",
649
+ alignItems: "center"
896
650
  },
897
651
  onClick: () => {
898
652
  onSelect(text);
@@ -1085,27 +839,29 @@ const CanyonReport = ({ value, name, dataSource, onSelect }) => {
1085
839
  const [fileContent, setFileContent] = useState("");
1086
840
  const [fileCodeChange, setFileCodeChange] = useState([]);
1087
841
  const [onlyChange, setOnlyChange] = useState(Boolean(false));
1088
- const rootClassName = useMemo(() => `report-scope-${Math.random().toString(36).slice(2, 9)}`, []);
842
+ const rootClassName = useMemo(() => `report-scope-${Math.random().toString(36).slice(2, 9)} canyonjs-report-html`, []);
1089
843
  function onChangeOnlyChange(v) {
1090
844
  setOnlyChange(v);
1091
845
  }
1092
- async function newOnSelect(val) {
1093
- const res = await onSelect(val);
1094
- setFileContent(res.fileContent || "");
1095
- setFileCoverage(res.fileCoverage || {});
1096
- setFileCodeChange(res.fileCodeChange || "");
1097
- return res;
1098
- }
846
+ const newOnSelect = useMemo(() => {
847
+ return async (val) => {
848
+ const res = await onSelect(val);
849
+ setFileContent(res.fileContent || "");
850
+ setFileCoverage(res.fileCoverage || {});
851
+ setFileCodeChange(res.fileCodeChange || "");
852
+ return res;
853
+ };
854
+ }, [onSelect]);
1099
855
  useEffect(() => {
1100
856
  newOnSelect(value);
1101
- }, []);
857
+ }, [newOnSelect, value]);
1102
858
  const isFile = useMemo(() => {
1103
859
  return /\.(js|jsx|ts|tsx|vue)$/.test(value);
1104
860
  }, [value]);
1105
861
  const mode = useMemo(() => {
1106
862
  if (isFile) return "file";
1107
863
  return showMode;
1108
- }, [showMode, value]);
864
+ }, [showMode, isFile]);
1109
865
  const isFileDataReady = useMemo(() => {
1110
866
  const hasCoverage = fileCoverage && Object.keys(fileCoverage).length > 0;
1111
867
  const hasContent = fileContent.length > 0;
@@ -1171,9 +927,9 @@ const CanyonReport = ({ value, name, dataSource, onSelect }) => {
1171
927
  children: /* @__PURE__ */ jsx(RIf_default, {
1172
928
  condition: isFileDataReady,
1173
929
  children: /* @__PURE__ */ jsx(CoverageDetail_default, {
1174
- fileContent,
1175
- fileCoverage,
1176
- fileCodeChange
930
+ source: fileContent,
931
+ coverage: fileCoverage,
932
+ diff: fileCodeChange
1177
933
  })
1178
934
  })
1179
935
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canyonjs/report-component",
3
3
  "type": "module",
4
- "version": "0.0.1-beta.12",
4
+ "version": "0.0.1-beta.16",
5
5
  "author": "Travis Zhang<https://github.com/travzhang>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -14,7 +14,8 @@
14
14
  "license": "MIT",
15
15
  "exports": {
16
16
  ".": "./dist/index.js",
17
- "./package.json": "./package.json"
17
+ "./package.json": "./package.json",
18
+ "./style.css": "./dist/style.css"
18
19
  },
19
20
  "main": "./dist/index.js",
20
21
  "module": "./dist/index.js",
@@ -22,9 +23,6 @@
22
23
  "files": [
23
24
  "dist"
24
25
  ],
25
- "publishConfig": {
26
- "access": "public"
27
- },
28
26
  "peerDependencies": {
29
27
  "react": "^19.2.0",
30
28
  "react-dom": "^19.2.0"
@@ -38,8 +36,8 @@
38
36
  "@vitejs/plugin-react": "^5.1.1",
39
37
  "istanbul-lib-coverage": "^3.2.2",
40
38
  "tsdown": "^0.17.0",
41
- "typescript": "^5.9.3",
42
- "vite": "^7.2.6"
39
+ "@typescript/native-preview": "^7.0.0-dev.20251215.1",
40
+ "vite": "8.0.0-beta.0"
43
41
  },
44
42
  "dependencies": {
45
43
  "@ant-design/icons": "^6.1.0",
@@ -54,7 +52,6 @@
54
52
  "build": "tsdown",
55
53
  "dev": "tsdown --watch",
56
54
  "play": "vite",
57
- "test": "vitest",
58
- "typecheck": "tsc --noEmit"
55
+ "typecheck": "tsgo --noEmit"
59
56
  }
60
57
  }