as-test 1.1.2 → 1.1.3
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/CHANGELOG.md +3 -0
- package/assembly/coverage.ts +12 -0
- package/assembly/index.ts +16 -0
- package/assembly/src/suite.ts +2 -0
- package/assembly/test.ts +85 -0
- package/bin/commands/run-core.js +10 -0
- package/bin/commands/run.js +3 -1
- package/bin/commands/test.js +3 -1
- package/bin/coverage-points.js +207 -2
- package/bin/index.js +56 -2
- package/bin/reporters/default.js +120 -31
- package/package.json +2 -1
- package/transform/lib/coverage.js +219 -70
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
- update build and run faliures to provide helpful error messages and reproduction commands and instructions
|
|
6
6
|
- remove confirmation from ast clean
|
|
7
|
+
- feat: make coverage gaps hierarchical and easier to scan, with parent-before-child grouping, tree-style connectors, collapsed nested gaps by default, and `--show-coverage=all` / `--verbose` expansion.
|
|
8
|
+
- feat: add richer coverage point names including `DefaultValue`, `Ternary`, `IfBranch`, `Assignment`, `Loop`, `Return`, and `Throw` so uncovered points describe the actual construct instead of falling back to broad labels.
|
|
9
|
+
- fix: make coverage snippets underline the emitted construct span instead of recomputing from the raw column, so cases like inline `if (...) ...` and assignments highlight the right text.
|
|
7
10
|
|
|
8
11
|
## 2026-05-12 -v1.1.1
|
|
9
12
|
|
package/assembly/coverage.ts
CHANGED
|
@@ -4,6 +4,10 @@ export class CoverPoint {
|
|
|
4
4
|
public line: i32 = 0;
|
|
5
5
|
public column: i32 = 0;
|
|
6
6
|
public type: string = "";
|
|
7
|
+
public parentHash: string = "";
|
|
8
|
+
public scopeKind: string = "";
|
|
9
|
+
public scopeName: string = "";
|
|
10
|
+
public depth: i32 = 0;
|
|
7
11
|
public executed: boolean = false;
|
|
8
12
|
}
|
|
9
13
|
|
|
@@ -29,6 +33,10 @@ export function __REGISTER_RAW(
|
|
|
29
33
|
line: i32,
|
|
30
34
|
column: i32,
|
|
31
35
|
type: string,
|
|
36
|
+
parentHash: string = "",
|
|
37
|
+
scopeKind: string = "",
|
|
38
|
+
scopeName: string = "",
|
|
39
|
+
depth: i32 = 0,
|
|
32
40
|
): void {
|
|
33
41
|
if (Coverage.SN.allIndex.has(hash)) return;
|
|
34
42
|
const point = new CoverPoint();
|
|
@@ -37,6 +45,10 @@ export function __REGISTER_RAW(
|
|
|
37
45
|
point.line = line;
|
|
38
46
|
point.column = column;
|
|
39
47
|
point.type = type;
|
|
48
|
+
point.parentHash = parentHash;
|
|
49
|
+
point.scopeKind = scopeKind;
|
|
50
|
+
point.scopeName = scopeName;
|
|
51
|
+
point.depth = depth;
|
|
40
52
|
Coverage.SN.points++;
|
|
41
53
|
Coverage.SN.allIndex.set(hash, Coverage.SN.all.length);
|
|
42
54
|
Coverage.SN.all.push(point);
|
package/assembly/index.ts
CHANGED
|
@@ -548,6 +548,10 @@ class CoveragePointReport {
|
|
|
548
548
|
column: i32 = 0;
|
|
549
549
|
type: string = "";
|
|
550
550
|
executed: bool = false;
|
|
551
|
+
parentHash: string = "";
|
|
552
|
+
scopeKind: string = "";
|
|
553
|
+
scopeName: string = "";
|
|
554
|
+
depth: i32 = 0;
|
|
551
555
|
|
|
552
556
|
serialize(): string {
|
|
553
557
|
return (
|
|
@@ -563,6 +567,14 @@ class CoveragePointReport {
|
|
|
563
567
|
quote(this.type) +
|
|
564
568
|
',"executed":' +
|
|
565
569
|
(this.executed ? "true" : "false") +
|
|
570
|
+
',"parentHash":' +
|
|
571
|
+
quote(this.parentHash) +
|
|
572
|
+
',"scopeKind":' +
|
|
573
|
+
quote(this.scopeKind) +
|
|
574
|
+
',"scopeName":' +
|
|
575
|
+
quote(this.scopeName) +
|
|
576
|
+
',"depth":' +
|
|
577
|
+
this.depth.toString() +
|
|
566
578
|
"}"
|
|
567
579
|
);
|
|
568
580
|
}
|
|
@@ -632,6 +644,10 @@ function toCoveragePointReport(point: CoverPoint): CoveragePointReport {
|
|
|
632
644
|
out.column = point.column;
|
|
633
645
|
out.type = point.type;
|
|
634
646
|
out.executed = point.executed;
|
|
647
|
+
out.parentHash = point.parentHash;
|
|
648
|
+
out.scopeKind = point.scopeKind;
|
|
649
|
+
out.scopeName = point.scopeName;
|
|
650
|
+
out.depth = point.depth;
|
|
635
651
|
return out;
|
|
636
652
|
}
|
|
637
653
|
|
package/assembly/src/suite.ts
CHANGED
package/assembly/test.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from "as-test";
|
|
2
|
+
// Import JSON and bs directly from json-as.
|
|
3
|
+
// bs import prevents json-as transform from adding broken pnpm paths.
|
|
4
|
+
import { JSON } from "json-as/assembly";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@json
|
|
8
|
+
class SimpleData {
|
|
9
|
+
name: string = "";
|
|
10
|
+
count: i32 = 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@json
|
|
15
|
+
class NestedData {
|
|
16
|
+
id: string = "";
|
|
17
|
+
items: string[] = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("JSON", () => {
|
|
21
|
+
describe("stringify", () => {
|
|
22
|
+
it("should serialize a simple class", () => {
|
|
23
|
+
const data = new SimpleData();
|
|
24
|
+
data.name = "test";
|
|
25
|
+
data.count = 42;
|
|
26
|
+
|
|
27
|
+
const json = JSON.stringify(data);
|
|
28
|
+
|
|
29
|
+
expect(json).toBe('{"name":"test","count":42}');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should serialize a class with arrays", () => {
|
|
33
|
+
const data = new NestedData();
|
|
34
|
+
data.id = "abc123";
|
|
35
|
+
data.items = ["item1", "item2"];
|
|
36
|
+
|
|
37
|
+
const json = JSON.stringify(data);
|
|
38
|
+
|
|
39
|
+
expect(json).toBe('{"id":"abc123","items":["item1","item2"]}');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should serialize primitive types", () => {
|
|
43
|
+
expect(JSON.stringify<i32>(42)).toBe("42");
|
|
44
|
+
expect(JSON.stringify<bool>(true)).toBe("true");
|
|
45
|
+
expect(JSON.stringify<string>("hello")).toBe('"hello"');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should serialize arrays", () => {
|
|
49
|
+
const arr: i32[] = [1, 2, 3];
|
|
50
|
+
expect(JSON.stringify(arr)).toBe("[1,2,3]");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("parse", () => {
|
|
55
|
+
it("should deserialize a simple class", () => {
|
|
56
|
+
const json = '{"name":"test","count":42}';
|
|
57
|
+
const data = JSON.parse<SimpleData>(json);
|
|
58
|
+
|
|
59
|
+
expect(data.name).toBe("test");
|
|
60
|
+
expect(data.count).toBe(42);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should deserialize a class with arrays", () => {
|
|
64
|
+
const json = '{"id":"abc123","items":["item1","item2"]}';
|
|
65
|
+
const data = JSON.parse<NestedData>(json);
|
|
66
|
+
|
|
67
|
+
expect(data.id).toBe("abc123");
|
|
68
|
+
expect(data.items.length).toBe(2);
|
|
69
|
+
expect(data.items[0]).toBe("item1");
|
|
70
|
+
expect(data.items[1]).toBe("item2");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should round-trip data correctly", () => {
|
|
74
|
+
const original = new SimpleData();
|
|
75
|
+
original.name = "round-trip";
|
|
76
|
+
original.count = 123;
|
|
77
|
+
|
|
78
|
+
const json = JSON.stringify(original);
|
|
79
|
+
const restored = JSON.parse<SimpleData>(json);
|
|
80
|
+
|
|
81
|
+
expect(restored.name).toBe(original.name);
|
|
82
|
+
expect(restored.count).toBe(original.count);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
package/bin/commands/run-core.js
CHANGED
|
@@ -719,6 +719,8 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
719
719
|
clean: cleanOutput,
|
|
720
720
|
snapshotEnabled,
|
|
721
721
|
showCoverage,
|
|
722
|
+
showCoverageAll: Boolean(flags.showCoverageAll),
|
|
723
|
+
verbose: Boolean(flags.verbose),
|
|
722
724
|
buildTime,
|
|
723
725
|
snapshotSummary,
|
|
724
726
|
coverageSummary,
|
|
@@ -1204,6 +1206,10 @@ function normalizeCoverage(value) {
|
|
|
1204
1206
|
column: Number(p.column ?? 0),
|
|
1205
1207
|
type: String(p.type ?? ""),
|
|
1206
1208
|
executed: Boolean(p.executed),
|
|
1209
|
+
parentHash: String(p.parentHash ?? ""),
|
|
1210
|
+
scopeKind: String(p.scopeKind ?? ""),
|
|
1211
|
+
scopeName: String(p.scopeName ?? ""),
|
|
1212
|
+
depth: Number(p.depth ?? 0),
|
|
1207
1213
|
};
|
|
1208
1214
|
})
|
|
1209
1215
|
.filter((point) => point.file.length > 0);
|
|
@@ -1475,10 +1481,14 @@ function matchesCoverageTextPattern(value, pattern) {
|
|
|
1475
1481
|
return globPatternToRegExp(normalized).test(value);
|
|
1476
1482
|
}
|
|
1477
1483
|
function compareCoveragePoints(a, b) {
|
|
1484
|
+
const depthA = a.depth ?? 0;
|
|
1485
|
+
const depthB = b.depth ?? 0;
|
|
1478
1486
|
if (a.line !== b.line)
|
|
1479
1487
|
return a.line - b.line;
|
|
1480
1488
|
if (a.column !== b.column)
|
|
1481
1489
|
return a.column - b.column;
|
|
1490
|
+
if (depthA !== depthB)
|
|
1491
|
+
return depthA - depthB;
|
|
1482
1492
|
if (a.type !== b.type)
|
|
1483
1493
|
return a.type.localeCompare(b.type);
|
|
1484
1494
|
return a.hash.localeCompare(b.hash);
|
package/bin/commands/run.js
CHANGED
|
@@ -4,12 +4,14 @@ export async function executeRunCommand(rawArgs, flags, configPath, selectedMode
|
|
|
4
4
|
const suiteSelectors = deps.resolveSuiteSelectors(rawArgs, "run");
|
|
5
5
|
const listFlags = deps.resolveListFlags(rawArgs, "run");
|
|
6
6
|
const featureToggles = deps.resolveFeatureToggles(rawArgs, "run");
|
|
7
|
+
const showCoverageMode = deps.resolveShowCoverageMode(rawArgs, "run");
|
|
7
8
|
const runFlags = {
|
|
8
9
|
snapshot: !flags.includes("--no-snapshot"),
|
|
9
10
|
createSnapshots: flags.includes("--create-snapshots"),
|
|
10
11
|
overwriteSnapshots: flags.includes("--overwrite-snapshots"),
|
|
11
12
|
clean: flags.includes("--clean"),
|
|
12
|
-
showCoverage:
|
|
13
|
+
showCoverage: showCoverageMode != undefined,
|
|
14
|
+
showCoverageAll: showCoverageMode == "all",
|
|
13
15
|
verbose: flags.includes("--verbose"),
|
|
14
16
|
...deps.resolveParallelJobs(rawArgs, "run"),
|
|
15
17
|
coverage: featureToggles.coverage,
|
package/bin/commands/test.js
CHANGED
|
@@ -8,12 +8,14 @@ export async function executeTestCommand(rawArgs, flags, configPath, selectedMod
|
|
|
8
8
|
tryAs: featureToggles.tryAs,
|
|
9
9
|
coverage: featureToggles.coverage,
|
|
10
10
|
};
|
|
11
|
+
const showCoverageMode = deps.resolveShowCoverageMode(rawArgs, "test");
|
|
11
12
|
const runFlags = {
|
|
12
13
|
snapshot: !flags.includes("--no-snapshot"),
|
|
13
14
|
createSnapshots: flags.includes("--create-snapshots"),
|
|
14
15
|
overwriteSnapshots: flags.includes("--overwrite-snapshots"),
|
|
15
16
|
clean: flags.includes("--clean"),
|
|
16
|
-
showCoverage:
|
|
17
|
+
showCoverage: showCoverageMode != undefined,
|
|
18
|
+
showCoverageAll: showCoverageMode == "all",
|
|
17
19
|
verbose: flags.includes("--verbose"),
|
|
18
20
|
...deps.resolveParallelJobs(rawArgs, "test"),
|
|
19
21
|
coverage: featureToggles.coverage,
|
package/bin/coverage-points.js
CHANGED
|
@@ -13,7 +13,61 @@ export function describeCoveragePoint(file, line, column, fallbackType) {
|
|
|
13
13
|
highlightEnd: 0,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
const
|
|
16
|
+
const parameter = detectCoverageParameter(context.visible, context.focus, fallbackType);
|
|
17
|
+
if (parameter) {
|
|
18
|
+
return {
|
|
19
|
+
displayType: parameter.type,
|
|
20
|
+
subjectName: parameter.name,
|
|
21
|
+
visible: context.visible,
|
|
22
|
+
focus: context.focus,
|
|
23
|
+
highlightStart: parameter.start,
|
|
24
|
+
highlightEnd: parameter.end,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const ternary = detectCoverageTernary(context.visible, context.focus, fallbackType);
|
|
28
|
+
if (ternary) {
|
|
29
|
+
return {
|
|
30
|
+
displayType: ternary.type,
|
|
31
|
+
subjectName: null,
|
|
32
|
+
visible: context.visible,
|
|
33
|
+
focus: context.focus,
|
|
34
|
+
highlightStart: ternary.start,
|
|
35
|
+
highlightEnd: ternary.end,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const ifBranch = detectCoverageIfBranch(context.visible, fallbackType);
|
|
39
|
+
if (ifBranch) {
|
|
40
|
+
return {
|
|
41
|
+
displayType: ifBranch.type,
|
|
42
|
+
subjectName: null,
|
|
43
|
+
visible: context.visible,
|
|
44
|
+
focus: context.focus,
|
|
45
|
+
highlightStart: ifBranch.start,
|
|
46
|
+
highlightEnd: ifBranch.end,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const assignment = detectCoverageAssignment(context.visible, fallbackType);
|
|
50
|
+
if (assignment) {
|
|
51
|
+
return {
|
|
52
|
+
displayType: assignment.type,
|
|
53
|
+
subjectName: null,
|
|
54
|
+
visible: context.visible,
|
|
55
|
+
focus: context.focus,
|
|
56
|
+
highlightStart: assignment.start,
|
|
57
|
+
highlightEnd: assignment.end,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const declarationAllowed = fallbackType == "Expression" ||
|
|
61
|
+
fallbackType == "Block" ||
|
|
62
|
+
fallbackType == "Function" ||
|
|
63
|
+
fallbackType == "Method" ||
|
|
64
|
+
fallbackType == "Constructor" ||
|
|
65
|
+
fallbackType == "Variable" ||
|
|
66
|
+
fallbackType == "Property" ||
|
|
67
|
+
fallbackType == "Call";
|
|
68
|
+
const declaration = declarationAllowed
|
|
69
|
+
? detectCoverageDeclaration(context.visible)
|
|
70
|
+
: null;
|
|
17
71
|
if (declaration) {
|
|
18
72
|
const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(context.visible, context.focus);
|
|
19
73
|
return {
|
|
@@ -25,7 +79,10 @@ export function describeCoveragePoint(file, line, column, fallbackType) {
|
|
|
25
79
|
highlightEnd,
|
|
26
80
|
};
|
|
27
81
|
}
|
|
28
|
-
const
|
|
82
|
+
const callAllowed = fallbackType == "Expression" || fallbackType == "Call";
|
|
83
|
+
const call = callAllowed
|
|
84
|
+
? detectCoverageCall(context.visible, context.focus)
|
|
85
|
+
: null;
|
|
29
86
|
if (call) {
|
|
30
87
|
return {
|
|
31
88
|
displayType: "Call",
|
|
@@ -130,6 +187,147 @@ function detectCoverageDeclaration(visible) {
|
|
|
130
187
|
}
|
|
131
188
|
return null;
|
|
132
189
|
}
|
|
190
|
+
function detectCoverageParameter(visible, focus, fallbackType) {
|
|
191
|
+
const inlineParameter = detectCoverageInlineParameter(visible, focus, fallbackType);
|
|
192
|
+
if (inlineParameter) {
|
|
193
|
+
return inlineParameter;
|
|
194
|
+
}
|
|
195
|
+
const openParen = visible.indexOf("(");
|
|
196
|
+
const closeParen = visible.lastIndexOf(")");
|
|
197
|
+
if (openParen == -1 || closeParen == -1 || closeParen <= openParen) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (focus <= openParen || focus >= closeParen) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const params = visible.slice(openParen + 1, closeParen);
|
|
204
|
+
const matches = [
|
|
205
|
+
...params.matchAll(/([A-Za-z_]\w*)\s*:\s*[^,)=]+(?:=\s*[^,)]*)?/g),
|
|
206
|
+
];
|
|
207
|
+
if (!matches.length)
|
|
208
|
+
return null;
|
|
209
|
+
for (const match of matches) {
|
|
210
|
+
const localStart = match.index ?? -1;
|
|
211
|
+
if (localStart == -1)
|
|
212
|
+
continue;
|
|
213
|
+
const localEnd = localStart + match[0].length;
|
|
214
|
+
const absoluteStart = openParen + 1 + localStart;
|
|
215
|
+
const absoluteEnd = openParen + 1 + localEnd;
|
|
216
|
+
if (focus < absoluteStart || focus > absoluteEnd)
|
|
217
|
+
continue;
|
|
218
|
+
const name = match[1] ?? null;
|
|
219
|
+
if (!name)
|
|
220
|
+
return null;
|
|
221
|
+
const nameOffset = match[0].indexOf(name);
|
|
222
|
+
const equalsOffset = match[0].indexOf("=");
|
|
223
|
+
if (fallbackType == "DefaultValue" && equalsOffset != -1) {
|
|
224
|
+
const valueStart = absoluteStart + equalsOffset + 1;
|
|
225
|
+
const valueVisibleStart = skipCoverageWhitespace(visible, valueStart);
|
|
226
|
+
const [start, end] = resolveCoverageHighlightSpan(visible, Math.max(valueVisibleStart, focus));
|
|
227
|
+
return {
|
|
228
|
+
type: "DefaultValue",
|
|
229
|
+
name,
|
|
230
|
+
start,
|
|
231
|
+
end,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
type: fallbackType == "Parameter" ? "Parameter" : "Property",
|
|
236
|
+
name,
|
|
237
|
+
start: absoluteStart + nameOffset,
|
|
238
|
+
end: absoluteStart + nameOffset + name.length,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
function detectCoverageInlineParameter(visible, focus, fallbackType) {
|
|
244
|
+
const match = visible.match(/^([A-Za-z_]\w*)\s*:\s*[^=,]+(?:=\s*[^,]+)?[,]?$/);
|
|
245
|
+
if (!match)
|
|
246
|
+
return null;
|
|
247
|
+
const name = match[1] ?? null;
|
|
248
|
+
if (!name)
|
|
249
|
+
return null;
|
|
250
|
+
const nameStart = visible.indexOf(name);
|
|
251
|
+
const nameEnd = nameStart + name.length;
|
|
252
|
+
const equalsIndex = visible.indexOf("=");
|
|
253
|
+
if (fallbackType == "DefaultValue" && equalsIndex != -1) {
|
|
254
|
+
const valueStart = skipCoverageWhitespace(visible, equalsIndex + 1);
|
|
255
|
+
const [start, end] = resolveCoverageHighlightSpan(visible, Math.max(valueStart, focus));
|
|
256
|
+
return {
|
|
257
|
+
type: "DefaultValue",
|
|
258
|
+
name,
|
|
259
|
+
start,
|
|
260
|
+
end,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
type: fallbackType == "Parameter" ? "Parameter" : "Property",
|
|
265
|
+
name,
|
|
266
|
+
start: nameStart,
|
|
267
|
+
end: nameEnd,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function detectCoverageTernary(visible, focus, fallbackType) {
|
|
271
|
+
if (fallbackType != "Ternary" && fallbackType != "LogicalBranch") {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const q = visible.indexOf("?");
|
|
275
|
+
if (q == -1)
|
|
276
|
+
return null;
|
|
277
|
+
if (fallbackType == "LogicalBranch") {
|
|
278
|
+
const [start, end] = resolveCoverageHighlightSpan(visible, focus);
|
|
279
|
+
return { type: "LogicalBranch", start, end };
|
|
280
|
+
}
|
|
281
|
+
const colon = visible.indexOf(":", q + 1);
|
|
282
|
+
if (colon == -1) {
|
|
283
|
+
const [start, end] = resolveCoverageHighlightSpan(visible, focus);
|
|
284
|
+
return { type: "Ternary", start, end };
|
|
285
|
+
}
|
|
286
|
+
const branchStart = focus <= colon ? q + 1 : colon + 1;
|
|
287
|
+
const normalizedStart = skipCoverageWhitespace(visible, branchStart);
|
|
288
|
+
const [start, end] = resolveCoverageHighlightSpan(visible, Math.max(normalizedStart, focus));
|
|
289
|
+
return { type: "Ternary", start, end };
|
|
290
|
+
}
|
|
291
|
+
function detectCoverageIfBranch(visible, fallbackType) {
|
|
292
|
+
if (fallbackType != "IfBranch")
|
|
293
|
+
return null;
|
|
294
|
+
const match = visible.match(/^if\s*\(([^)]*)\)/);
|
|
295
|
+
if (!match)
|
|
296
|
+
return null;
|
|
297
|
+
const full = match[0];
|
|
298
|
+
const condition = match[1] ?? "";
|
|
299
|
+
const openParen = full.indexOf("(");
|
|
300
|
+
const conditionPadding = condition.length
|
|
301
|
+
? condition.length - condition.trimStart().length
|
|
302
|
+
: 0;
|
|
303
|
+
const conditionStart = openParen == -1 ? -1 : openParen + 1 + conditionPadding;
|
|
304
|
+
if (conditionStart == -1 || !condition.length) {
|
|
305
|
+
return { type: "IfBranch", start: 0, end: full.length };
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
type: "IfBranch",
|
|
309
|
+
start: conditionStart,
|
|
310
|
+
end: conditionStart + condition.length,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function detectCoverageAssignment(visible, fallbackType) {
|
|
314
|
+
if (fallbackType != "Assignment")
|
|
315
|
+
return null;
|
|
316
|
+
const match = visible.match(/([A-Za-z_]\w*(?:\.[A-Za-z_]\w*|\[[^\]]+\])?)\s*(=|\+=|-=|\*=|\*\*=|\/=|%=|<<=|>>=|>>>=|&=|\|=|\^=)/);
|
|
317
|
+
if (!match)
|
|
318
|
+
return null;
|
|
319
|
+
const full = match[0];
|
|
320
|
+
const lhs = match[1] ?? "";
|
|
321
|
+
const operator = match[2] ?? "=";
|
|
322
|
+
const fullStart = visible.indexOf(full);
|
|
323
|
+
const lhsStart = fullStart + full.indexOf(lhs);
|
|
324
|
+
const operatorStart = fullStart + full.lastIndexOf(operator);
|
|
325
|
+
return {
|
|
326
|
+
type: "Assignment",
|
|
327
|
+
start: lhsStart,
|
|
328
|
+
end: operatorStart + operator.length,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
133
331
|
function detectCoverageCall(visible, focus) {
|
|
134
332
|
const matches = [...visible.matchAll(/\b([A-Za-z_]\w*)(?:<[^>()]+>)?\s*\(/g)];
|
|
135
333
|
if (!matches.length)
|
|
@@ -171,3 +369,10 @@ function detectCoverageCall(visible, focus) {
|
|
|
171
369
|
function isCoverageBoundary(ch) {
|
|
172
370
|
return /[\s()[\]{}.,;:+\-*/%&|^!?=<>]/.test(ch);
|
|
173
371
|
}
|
|
372
|
+
function skipCoverageWhitespace(visible, index) {
|
|
373
|
+
let current = Math.max(0, Math.min(visible.length - 1, index));
|
|
374
|
+
while (current < visible.length - 1 && /\s/.test(visible.charAt(current))) {
|
|
375
|
+
current++;
|
|
376
|
+
}
|
|
377
|
+
return current;
|
|
378
|
+
}
|
package/bin/index.js
CHANGED
|
@@ -78,6 +78,7 @@ else if (COMMANDS.includes(args[0])) {
|
|
|
78
78
|
resolveParallelJobs,
|
|
79
79
|
resolveBrowserOverride,
|
|
80
80
|
resolveReporterOverride,
|
|
81
|
+
resolveShowCoverageMode,
|
|
81
82
|
resolveExecutionModes,
|
|
82
83
|
listExecutionPlan,
|
|
83
84
|
runRuntimeModes,
|
|
@@ -96,6 +97,7 @@ else if (COMMANDS.includes(args[0])) {
|
|
|
96
97
|
resolveParallelJobs,
|
|
97
98
|
resolveBrowserOverride,
|
|
98
99
|
resolveReporterOverride,
|
|
100
|
+
resolveShowCoverageMode,
|
|
99
101
|
resolveFuzzOverrides,
|
|
100
102
|
resolveExecutionModes,
|
|
101
103
|
listExecutionPlan,
|
|
@@ -277,7 +279,7 @@ function printCommandHelp(command) {
|
|
|
277
279
|
process.stdout.write(" --create-snapshots Create missing snapshot entries\n");
|
|
278
280
|
process.stdout.write(" --overwrite-snapshots Overwrite existing snapshot entries on mismatch\n");
|
|
279
281
|
process.stdout.write(" --no-snapshot Disable snapshot assertions for this run\n");
|
|
280
|
-
process.stdout.write(" --show-coverage
|
|
282
|
+
process.stdout.write(" --show-coverage[=all] Print uncovered coverage point details; use =all to expand nested gaps\n");
|
|
281
283
|
process.stdout.write(" --suite <name[,name...]> Filter results to matching suite names or suite slug paths\n");
|
|
282
284
|
process.stdout.write(" --suites <name[,name...]> Alias for --suite\n");
|
|
283
285
|
process.stdout.write(" --enable <feature> Enable feature (coverage|try-as)\n");
|
|
@@ -305,7 +307,7 @@ function printCommandHelp(command) {
|
|
|
305
307
|
process.stdout.write(" --create-snapshots Create missing snapshot entries\n");
|
|
306
308
|
process.stdout.write(" --overwrite-snapshots Overwrite existing snapshot entries on mismatch\n");
|
|
307
309
|
process.stdout.write(" --no-snapshot Disable snapshot assertions for this run\n");
|
|
308
|
-
process.stdout.write(" --show-coverage
|
|
310
|
+
process.stdout.write(" --show-coverage[=all] Print uncovered coverage point details; use =all to expand nested gaps\n");
|
|
309
311
|
process.stdout.write(" --suite <name[,name...]> Filter results to matching suite names or suite slug paths\n");
|
|
310
312
|
process.stdout.write(" --suites <name[,name...]> Alias for --suite\n");
|
|
311
313
|
process.stdout.write(" --enable <feature> Enable feature (coverage|try-as)\n");
|
|
@@ -462,8 +464,15 @@ function resolveCommandArgs(rawArgs, command) {
|
|
|
462
464
|
arg == "--build-jobs" ||
|
|
463
465
|
arg == "--run-jobs" ||
|
|
464
466
|
arg == "--browser" ||
|
|
467
|
+
arg == "--show-coverage" ||
|
|
465
468
|
arg == "--fuzz-runs" ||
|
|
466
469
|
arg == "--fuzz-seed") {
|
|
470
|
+
if (arg == "--show-coverage") {
|
|
471
|
+
const next = rawArgs[i + 1];
|
|
472
|
+
if (next == "all")
|
|
473
|
+
i++;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
467
476
|
i++;
|
|
468
477
|
continue;
|
|
469
478
|
}
|
|
@@ -476,6 +485,7 @@ function resolveCommandArgs(rawArgs, command) {
|
|
|
476
485
|
arg.startsWith("--build-jobs=") ||
|
|
477
486
|
arg.startsWith("--run-jobs=") ||
|
|
478
487
|
arg.startsWith("--browser=") ||
|
|
488
|
+
arg.startsWith("--show-coverage=") ||
|
|
479
489
|
arg.startsWith("--fuzz-runs=") ||
|
|
480
490
|
arg.startsWith("--fuzz-seed=")) {
|
|
481
491
|
continue;
|
|
@@ -762,6 +772,34 @@ function resolveReporterOverride(rawArgs, command) {
|
|
|
762
772
|
}
|
|
763
773
|
return undefined;
|
|
764
774
|
}
|
|
775
|
+
function resolveShowCoverageMode(rawArgs, command) {
|
|
776
|
+
let seenCommand = false;
|
|
777
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
778
|
+
const arg = rawArgs[i];
|
|
779
|
+
if (!seenCommand) {
|
|
780
|
+
if (arg == command)
|
|
781
|
+
seenCommand = true;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (arg == "--show-coverage") {
|
|
785
|
+
const next = rawArgs[i + 1];
|
|
786
|
+
if (next == "all")
|
|
787
|
+
return "all";
|
|
788
|
+
return "collapsed";
|
|
789
|
+
}
|
|
790
|
+
if (arg.startsWith("--show-coverage=")) {
|
|
791
|
+
const value = arg.slice("--show-coverage=".length).trim();
|
|
792
|
+
if (!value.length) {
|
|
793
|
+
throw new Error("--show-coverage requires a value when using =");
|
|
794
|
+
}
|
|
795
|
+
if (value != "all") {
|
|
796
|
+
throw new Error(`--show-coverage only supports "all" when given a value`);
|
|
797
|
+
}
|
|
798
|
+
return "all";
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
765
803
|
function resolveJobs(rawArgs, command) {
|
|
766
804
|
let seenCommand = false;
|
|
767
805
|
let parallel = false;
|
|
@@ -1140,6 +1178,8 @@ async function runTestSequential(runFlags, configPath, selectors, suiteSelectors
|
|
|
1140
1178
|
clean: runFlags.clean,
|
|
1141
1179
|
snapshotEnabled,
|
|
1142
1180
|
showCoverage: runFlags.showCoverage,
|
|
1181
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
1182
|
+
verbose: runFlags.verbose,
|
|
1143
1183
|
buildTime: getMergedIntervalDuration(buildIntervals),
|
|
1144
1184
|
snapshotSummary: summary.snapshotSummary,
|
|
1145
1185
|
coverageSummary: summary.coverageSummary,
|
|
@@ -1372,6 +1412,8 @@ async function runRuntimeMatrix(runFlags, configPath, selectors, suiteSelectors,
|
|
|
1372
1412
|
clean: runFlags.clean,
|
|
1373
1413
|
snapshotEnabled,
|
|
1374
1414
|
showCoverage: runFlags.showCoverage,
|
|
1415
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
1416
|
+
verbose: runFlags.verbose,
|
|
1375
1417
|
buildTime: 0,
|
|
1376
1418
|
snapshotSummary: summary.snapshotSummary,
|
|
1377
1419
|
coverageSummary: summary.coverageSummary,
|
|
@@ -1435,6 +1477,8 @@ async function runTestModes(runFlags, configPath, selectors, suiteSelectors, fuz
|
|
|
1435
1477
|
clean: runFlags.clean,
|
|
1436
1478
|
snapshotEnabled: effectiveRunFlags.snapshot !== false,
|
|
1437
1479
|
showCoverage: effectiveRunFlags.showCoverage,
|
|
1480
|
+
showCoverageAll: effectiveRunFlags.showCoverageAll,
|
|
1481
|
+
verbose: effectiveRunFlags.verbose,
|
|
1438
1482
|
buildTime: modeResult.summary.buildTime +
|
|
1439
1483
|
getMergedIntervalDuration(collectFuzzBuildIntervals(fuzzResults)),
|
|
1440
1484
|
snapshotSummary: modeResult.summary.snapshotSummary,
|
|
@@ -1572,6 +1616,8 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
|
|
|
1572
1616
|
clean: runFlags.clean,
|
|
1573
1617
|
snapshotEnabled,
|
|
1574
1618
|
showCoverage: runFlags.showCoverage,
|
|
1619
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
1620
|
+
verbose: runFlags.verbose,
|
|
1575
1621
|
buildTime: getMergedIntervalDuration(buildIntervals),
|
|
1576
1622
|
snapshotSummary: summary.snapshotSummary,
|
|
1577
1623
|
coverageSummary: summary.coverageSummary,
|
|
@@ -1664,6 +1710,8 @@ async function runRuntimeSingleParallel(runFlags, configPath, selectors, suiteSe
|
|
|
1664
1710
|
clean: runFlags.clean,
|
|
1665
1711
|
snapshotEnabled,
|
|
1666
1712
|
showCoverage: runFlags.showCoverage,
|
|
1713
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
1714
|
+
verbose: runFlags.verbose,
|
|
1667
1715
|
buildTime: 0,
|
|
1668
1716
|
snapshotSummary: summary.snapshotSummary,
|
|
1669
1717
|
coverageSummary: summary.coverageSummary,
|
|
@@ -1778,6 +1826,8 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, suiteSe
|
|
|
1778
1826
|
clean: runFlags.clean,
|
|
1779
1827
|
snapshotEnabled,
|
|
1780
1828
|
showCoverage: runFlags.showCoverage,
|
|
1829
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
1830
|
+
verbose: runFlags.verbose,
|
|
1781
1831
|
buildTime: getMergedIntervalDuration(buildIntervals),
|
|
1782
1832
|
snapshotSummary: summary.snapshotSummary,
|
|
1783
1833
|
coverageSummary: summary.coverageSummary,
|
|
@@ -1880,6 +1930,8 @@ async function runTestSingleParallel(runFlags, configPath, selectors, suiteSelec
|
|
|
1880
1930
|
clean: runFlags.clean,
|
|
1881
1931
|
snapshotEnabled,
|
|
1882
1932
|
showCoverage: runFlags.showCoverage,
|
|
1933
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
1934
|
+
verbose: runFlags.verbose,
|
|
1883
1935
|
buildTime: getMergedIntervalDuration(buildIntervals),
|
|
1884
1936
|
snapshotSummary: summary.snapshotSummary,
|
|
1885
1937
|
coverageSummary: summary.coverageSummary,
|
|
@@ -2013,6 +2065,8 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
|
|
|
2013
2065
|
clean: runFlags.clean,
|
|
2014
2066
|
snapshotEnabled,
|
|
2015
2067
|
showCoverage: runFlags.showCoverage,
|
|
2068
|
+
showCoverageAll: runFlags.showCoverageAll,
|
|
2069
|
+
verbose: runFlags.verbose,
|
|
2016
2070
|
buildTime: getMergedIntervalDuration(buildIntervals),
|
|
2017
2071
|
snapshotSummary: summary.snapshotSummary,
|
|
2018
2072
|
coverageSummary: summary.coverageSummary,
|
package/bin/reporters/default.js
CHANGED
|
@@ -3,7 +3,7 @@ import { diff } from "typer-diff";
|
|
|
3
3
|
import { readFileSync } from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { formatSpecDisplayPath, formatTime } from "../util.js";
|
|
6
|
-
import { describeCoveragePoint
|
|
6
|
+
import { describeCoveragePoint } from "../coverage-points.js";
|
|
7
7
|
export const createReporter = (context) => {
|
|
8
8
|
return new DefaultReporter(context);
|
|
9
9
|
};
|
|
@@ -288,7 +288,7 @@ class DefaultReporter {
|
|
|
288
288
|
if (event.coverageSummary.enabled) {
|
|
289
289
|
renderCoverageSummary(event.coverageSummary, event.showCoverage);
|
|
290
290
|
if (event.showCoverage && event.coverageSummary.uncovered) {
|
|
291
|
-
renderCoveragePoints(event.coverageSummary.files);
|
|
291
|
+
renderCoveragePoints(event.coverageSummary.files, Boolean(event.verbose || event.showCoverageAll));
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
renderTotals(event.stats, event);
|
|
@@ -831,7 +831,7 @@ function renderCoverageSummary(summary, showCoverage) {
|
|
|
831
831
|
console.log(chalk.dim(` ... ${ranked.length - 8} more files`));
|
|
832
832
|
}
|
|
833
833
|
}
|
|
834
|
-
function renderCoveragePoints(files) {
|
|
834
|
+
function renderCoveragePoints(files, expandNested) {
|
|
835
835
|
console.log("");
|
|
836
836
|
console.log(chalk.bold("Coverage Gaps"));
|
|
837
837
|
const sortedFiles = [...files].sort((a, b) => a.file.localeCompare(b.file));
|
|
@@ -842,28 +842,124 @@ function renderCoveragePoints(files) {
|
|
|
842
842
|
displayType: describeCoveragePoint(point.file, point.line, point.column, point.type).displayType,
|
|
843
843
|
})));
|
|
844
844
|
const layout = createCoverageGapLayout(missingPoints);
|
|
845
|
+
let renderedFileCount = 0;
|
|
846
|
+
let collapsedNestedPoints = 0;
|
|
845
847
|
for (const file of sortedFiles) {
|
|
846
|
-
const points = [...file.points].sort(
|
|
847
|
-
if (a.line != b.line)
|
|
848
|
-
return a.line - b.line;
|
|
849
|
-
if (a.column != b.column)
|
|
850
|
-
return a.column - b.column;
|
|
851
|
-
return a.type.localeCompare(b.type);
|
|
852
|
-
});
|
|
848
|
+
const points = [...file.points].sort(compareCoverageGapPoints);
|
|
853
849
|
const missing = points.filter((point) => !point.executed);
|
|
854
850
|
if (!missing.length)
|
|
855
851
|
continue;
|
|
852
|
+
if (renderedFileCount > 0) {
|
|
853
|
+
console.log("");
|
|
854
|
+
}
|
|
856
855
|
console.log(` ${chalk.bold(toRelativeResultPath(file.file))} ${chalk.dim(`(${missing.length} uncovered)`)}`);
|
|
856
|
+
const pointsByHash = new Map(points.map((point) => [point.hash, point]));
|
|
857
|
+
const childrenByParent = new Map();
|
|
858
|
+
const roots = [];
|
|
857
859
|
for (const point of points) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
860
|
+
const parentHash = point.parentHash ?? "";
|
|
861
|
+
if (parentHash.length && pointsByHash.has(parentHash)) {
|
|
862
|
+
const children = childrenByParent.get(parentHash) ?? [];
|
|
863
|
+
children.push(point);
|
|
864
|
+
childrenByParent.set(parentHash, children);
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
roots.push(point);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
const visibleRoots = roots.filter((point) => shouldRenderCoveragePoint(point, childrenByParent));
|
|
871
|
+
for (let i = 0; i < visibleRoots.length; i++) {
|
|
872
|
+
collapsedNestedPoints += renderCoveragePointTree(visibleRoots[i], childrenByParent, layout, [], i == visibleRoots.length - 1, expandNested);
|
|
865
873
|
}
|
|
874
|
+
renderedFileCount++;
|
|
866
875
|
}
|
|
876
|
+
if (!expandNested && collapsedNestedPoints > 0) {
|
|
877
|
+
console.log("");
|
|
878
|
+
console.log(chalk.dim(" Run with --show-coverage=all or --verbose to expand nested coverage gaps."));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function renderCoveragePointTree(point, childrenByParent, layout, ancestorHasNext, isLast, expandNested) {
|
|
882
|
+
const visibleChildren = [...(childrenByParent.get(point.hash) ?? [])]
|
|
883
|
+
.filter((child) => shouldRenderCoveragePoint(child, childrenByParent))
|
|
884
|
+
.sort(compareCoverageGapPoints);
|
|
885
|
+
const nestedUncoveredCount = countNestedUncoveredPoints(visibleChildren, childrenByParent);
|
|
886
|
+
if (!point.executed) {
|
|
887
|
+
renderCoverageGapLine(point, layout, ancestorHasNext, isLast);
|
|
888
|
+
if (nestedUncoveredCount > 0) {
|
|
889
|
+
if (expandNested) {
|
|
890
|
+
let rendered = 0;
|
|
891
|
+
for (let i = 0; i < visibleChildren.length; i++) {
|
|
892
|
+
rendered += renderCoveragePointTree(visibleChildren[i], childrenByParent, layout, [...ancestorHasNext, !isLast], i == visibleChildren.length - 1, expandNested);
|
|
893
|
+
}
|
|
894
|
+
return 1 + rendered;
|
|
895
|
+
}
|
|
896
|
+
const treePrefix = buildCoverageTreePrefix([...ancestorHasNext, !isLast], true);
|
|
897
|
+
console.log(` ${treePrefix}${chalk.dim(`(+${nestedUncoveredCount} nested uncovered point${nestedUncoveredCount == 1 ? "" : "s"})`)}`);
|
|
898
|
+
return nestedUncoveredCount;
|
|
899
|
+
}
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
if (nestedUncoveredCount <= 0)
|
|
903
|
+
return 0;
|
|
904
|
+
renderCoverageScopeHeader(point, layout, ancestorHasNext, isLast);
|
|
905
|
+
let rendered = 0;
|
|
906
|
+
for (let i = 0; i < visibleChildren.length; i++) {
|
|
907
|
+
rendered += renderCoveragePointTree(visibleChildren[i], childrenByParent, layout, [...ancestorHasNext, !isLast], i == visibleChildren.length - 1, expandNested);
|
|
908
|
+
}
|
|
909
|
+
return rendered;
|
|
910
|
+
}
|
|
911
|
+
function shouldRenderCoveragePoint(point, childrenByParent) {
|
|
912
|
+
if (!point.executed)
|
|
913
|
+
return true;
|
|
914
|
+
return (countNestedUncoveredPoints(childrenByParent.get(point.hash) ?? [], childrenByParent) > 0);
|
|
915
|
+
}
|
|
916
|
+
function countNestedUncoveredPoints(points, childrenByParent) {
|
|
917
|
+
let count = 0;
|
|
918
|
+
for (const point of points) {
|
|
919
|
+
if (!point.executed)
|
|
920
|
+
count++;
|
|
921
|
+
count += countNestedUncoveredPoints(childrenByParent.get(point.hash) ?? [], childrenByParent);
|
|
922
|
+
}
|
|
923
|
+
return count;
|
|
924
|
+
}
|
|
925
|
+
function renderCoverageGapLine(point, layout, ancestorHasNext, isLast) {
|
|
926
|
+
const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
|
|
927
|
+
const snippet = formatCoverageSnippet(point.file, point.line, point.column, point.type, ancestorHasNext.length);
|
|
928
|
+
const typeLabel = describeCoveragePoint(point.file, point.line, point.column, point.type).displayType.padEnd(layout.typeWidth + 6);
|
|
929
|
+
const locationLabel = location.padEnd(layout.locationWidth + 6);
|
|
930
|
+
const treePrefix = buildCoverageTreePrefix(ancestorHasNext, isLast);
|
|
931
|
+
const meta = `${typeLabel}${locationLabel}`;
|
|
932
|
+
console.log(` ${treePrefix}${chalk.dim(meta)} ${snippet}`);
|
|
933
|
+
}
|
|
934
|
+
function renderCoverageScopeHeader(point, layout, ancestorHasNext, isLast) {
|
|
935
|
+
const descriptor = describeCoveragePoint(point.file, point.line, point.column, point.type);
|
|
936
|
+
const label = point.scopeKind || descriptor.displayType;
|
|
937
|
+
const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
|
|
938
|
+
const locationLabel = location.padEnd(layout.locationWidth + 6);
|
|
939
|
+
const typeLabel = label.padEnd(layout.typeWidth + 6);
|
|
940
|
+
const snippet = formatCoverageSnippet(point.file, point.line, point.column, point.type, ancestorHasNext.length);
|
|
941
|
+
const treePrefix = buildCoverageTreePrefix(ancestorHasNext, isLast);
|
|
942
|
+
const meta = `${typeLabel}${locationLabel}`;
|
|
943
|
+
console.log(` ${treePrefix}${chalk.dim(meta)} ${chalk.dim(snippet)}`);
|
|
944
|
+
}
|
|
945
|
+
function buildCoverageTreePrefix(ancestorHasNext, isLast) {
|
|
946
|
+
let out = "";
|
|
947
|
+
for (const hasNext of ancestorHasNext) {
|
|
948
|
+
out += hasNext ? "│ " : " ";
|
|
949
|
+
}
|
|
950
|
+
out += isLast ? "└─" : "├─";
|
|
951
|
+
return chalk.dim(out);
|
|
952
|
+
}
|
|
953
|
+
function compareCoverageGapPoints(a, b) {
|
|
954
|
+
if (a.line != b.line)
|
|
955
|
+
return a.line - b.line;
|
|
956
|
+
if (a.column != b.column)
|
|
957
|
+
return a.column - b.column;
|
|
958
|
+
if ((a.depth ?? 0) != (b.depth ?? 0))
|
|
959
|
+
return (a.depth ?? 0) - (b.depth ?? 0);
|
|
960
|
+
if (a.type != b.type)
|
|
961
|
+
return a.type.localeCompare(b.type);
|
|
962
|
+
return a.hash.localeCompare(b.hash);
|
|
867
963
|
}
|
|
868
964
|
function renderCoverageBar(percent) {
|
|
869
965
|
const slots = 12;
|
|
@@ -877,32 +973,25 @@ function createCoverageGapLayout(points) {
|
|
|
877
973
|
.length), 1),
|
|
878
974
|
};
|
|
879
975
|
}
|
|
880
|
-
function formatCoverageSnippet(file, line, column) {
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
return "";
|
|
884
|
-
const expanded = sourceLine.replace(/\t/g, " ");
|
|
885
|
-
const firstNonWhitespace = expanded.search(/\S/);
|
|
886
|
-
if (firstNonWhitespace == -1)
|
|
887
|
-
return "";
|
|
888
|
-
const visible = expanded.slice(firstNonWhitespace).trimEnd();
|
|
976
|
+
function formatCoverageSnippet(file, line, column, fallbackType, _depth) {
|
|
977
|
+
const descriptor = describeCoveragePoint(file, line, column, fallbackType);
|
|
978
|
+
const visible = descriptor.visible;
|
|
889
979
|
if (!visible.length)
|
|
890
980
|
return "";
|
|
891
981
|
const maxWidth = 72;
|
|
892
|
-
const focus = Math.max(0, Math.min(visible.length - 1,
|
|
982
|
+
const focus = Math.max(0, Math.min(visible.length - 1, descriptor.focus));
|
|
893
983
|
if (visible.length <= maxWidth) {
|
|
894
|
-
return styleCoverageSnippetWindow(visible, 0, visible.length, focus);
|
|
984
|
+
return styleCoverageSnippetWindow(visible, 0, visible.length, focus, descriptor.highlightStart, descriptor.highlightEnd);
|
|
895
985
|
}
|
|
896
986
|
const start = Math.max(0, Math.min(visible.length - maxWidth, focus - Math.floor(maxWidth / 2)));
|
|
897
987
|
const end = Math.min(visible.length, start + maxWidth);
|
|
898
|
-
return styleCoverageSnippetWindow(visible, start, end, focus);
|
|
988
|
+
return styleCoverageSnippetWindow(visible, start, end, focus, descriptor.highlightStart, descriptor.highlightEnd);
|
|
899
989
|
}
|
|
900
|
-
function styleCoverageSnippetWindow(visible, start, end, focus) {
|
|
990
|
+
function styleCoverageSnippetWindow(visible, start, end, focus, highlightStart, highlightEnd) {
|
|
901
991
|
const prefix = start > 0 ? "..." : "";
|
|
902
992
|
const suffix = end < visible.length ? "..." : "";
|
|
903
993
|
const slice = visible.slice(start, end);
|
|
904
994
|
const localFocus = Math.max(0, Math.min(slice.length - 1, focus - start));
|
|
905
|
-
const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(visible, focus);
|
|
906
995
|
const localStart = Math.max(0, Math.min(slice.length, highlightStart - start));
|
|
907
996
|
const localEnd = Math.max(localStart + 1, Math.min(slice.length, highlightEnd - start));
|
|
908
997
|
if (!slice.length)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "as-test",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"author": "Jairus Tanaka",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"as-test": "./",
|
|
21
21
|
"assemblyscript": "^0.28.17",
|
|
22
22
|
"assemblyscript-prettier": "^3.0.4",
|
|
23
|
+
"json-as": "^1.3.5",
|
|
23
24
|
"prettier": "3.8.3",
|
|
24
25
|
"try-as": "^1.0.1",
|
|
25
26
|
"typescript": "^6.0.3",
|
|
@@ -2,12 +2,6 @@ import { BlockStatement, ExpressionStatement, Node, } from "assemblyscript/dist/
|
|
|
2
2
|
import { RangeTransform } from "./range.js";
|
|
3
3
|
import { isStdlib, SimpleParser } from "./util.js";
|
|
4
4
|
import { Visitor } from "./visitor.js";
|
|
5
|
-
var CoverType;
|
|
6
|
-
(function (CoverType) {
|
|
7
|
-
CoverType[CoverType["Function"] = 0] = "Function";
|
|
8
|
-
CoverType[CoverType["Expression"] = 1] = "Expression";
|
|
9
|
-
CoverType[CoverType["Block"] = 2] = "Block";
|
|
10
|
-
})(CoverType || (CoverType = {}));
|
|
11
5
|
const COVERAGE_IGNORED_CALLS = new Set([
|
|
12
6
|
"beforeAll",
|
|
13
7
|
"afterAll",
|
|
@@ -50,13 +44,106 @@ class CoverPoint {
|
|
|
50
44
|
hash = "";
|
|
51
45
|
line = 0;
|
|
52
46
|
column = 0;
|
|
53
|
-
type;
|
|
47
|
+
type = "Expression";
|
|
54
48
|
executed = false;
|
|
49
|
+
parentHash = "";
|
|
50
|
+
scopeKind = "";
|
|
51
|
+
scopeName = "";
|
|
52
|
+
depth = 0;
|
|
55
53
|
}
|
|
56
54
|
export class CoverageTransform extends Visitor {
|
|
57
55
|
mustImport = false;
|
|
58
56
|
points = new Map();
|
|
59
57
|
globalStatements = [];
|
|
58
|
+
scopeStack = [];
|
|
59
|
+
getCurrentScope() {
|
|
60
|
+
return this.scopeStack.length
|
|
61
|
+
? this.scopeStack[this.scopeStack.length - 1]
|
|
62
|
+
: null;
|
|
63
|
+
}
|
|
64
|
+
withScope(point, callback) {
|
|
65
|
+
this.scopeStack.push(point);
|
|
66
|
+
try {
|
|
67
|
+
callback();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
this.scopeStack.pop();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
assignParentMetadata(point) {
|
|
74
|
+
const currentScope = this.getCurrentScope();
|
|
75
|
+
if (!currentScope)
|
|
76
|
+
return;
|
|
77
|
+
point.parentHash = currentScope.hash;
|
|
78
|
+
point.scopeKind = currentScope.scopeKind;
|
|
79
|
+
point.scopeName = currentScope.scopeName;
|
|
80
|
+
point.depth = currentScope.depth + 1;
|
|
81
|
+
}
|
|
82
|
+
assignScopeMetadata(point, scopeKind, scopeName = null) {
|
|
83
|
+
point.scopeKind = scopeKind;
|
|
84
|
+
point.scopeName = scopeName ?? "";
|
|
85
|
+
}
|
|
86
|
+
createFunctionPoint(path, node, scopeKind, scopeName = null) {
|
|
87
|
+
const funcLc = getLineCol(node);
|
|
88
|
+
const point = new CoverPoint();
|
|
89
|
+
point.line = funcLc?.line;
|
|
90
|
+
point.column = funcLc?.column;
|
|
91
|
+
point.file = path;
|
|
92
|
+
point.type = scopeKind;
|
|
93
|
+
this.assignParentMetadata(point);
|
|
94
|
+
this.assignScopeMetadata(point, scopeKind, scopeName);
|
|
95
|
+
point.hash = hash(point);
|
|
96
|
+
return point;
|
|
97
|
+
}
|
|
98
|
+
createPoint(path, node, type) {
|
|
99
|
+
const lc = getLineCol(node);
|
|
100
|
+
const point = new CoverPoint();
|
|
101
|
+
point.line = lc?.line;
|
|
102
|
+
point.column = lc?.column;
|
|
103
|
+
point.file = path;
|
|
104
|
+
point.type = type;
|
|
105
|
+
this.assignParentMetadata(point);
|
|
106
|
+
point.hash = hash(point);
|
|
107
|
+
return point;
|
|
108
|
+
}
|
|
109
|
+
instrumentStatementBody(path, node, type) {
|
|
110
|
+
const point = this.createPoint(path, node, type);
|
|
111
|
+
const replacer = new RangeTransform(node);
|
|
112
|
+
const registerStmt = createRegisterStatement(point);
|
|
113
|
+
replacer.visit(registerStmt);
|
|
114
|
+
const coverStmt = createCoverStatement(point.hash, node);
|
|
115
|
+
replacer.visit(coverStmt);
|
|
116
|
+
this.globalStatements.push(registerStmt);
|
|
117
|
+
if (node.kind == 31) {
|
|
118
|
+
const block = node;
|
|
119
|
+
block.statements.unshift(coverStmt);
|
|
120
|
+
return block;
|
|
121
|
+
}
|
|
122
|
+
const coverBlock = Node.createBlockStatement([coverStmt], node.range);
|
|
123
|
+
replacer.visit(coverBlock);
|
|
124
|
+
coverBlock.statements.push(node);
|
|
125
|
+
return coverBlock;
|
|
126
|
+
}
|
|
127
|
+
isAssignmentOperator(operator) {
|
|
128
|
+
switch (operator) {
|
|
129
|
+
case 101:
|
|
130
|
+
case 102:
|
|
131
|
+
case 103:
|
|
132
|
+
case 104:
|
|
133
|
+
case 105:
|
|
134
|
+
case 106:
|
|
135
|
+
case 107:
|
|
136
|
+
case 108:
|
|
137
|
+
case 109:
|
|
138
|
+
case 110:
|
|
139
|
+
case 111:
|
|
140
|
+
case 112:
|
|
141
|
+
case 113:
|
|
142
|
+
return true;
|
|
143
|
+
default:
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
60
147
|
visitCallExpression(node) {
|
|
61
148
|
const callName = getCallName(node);
|
|
62
149
|
if (callName && COVERAGE_IGNORED_CALLS.has(callName)) {
|
|
@@ -89,7 +176,8 @@ export class CoverageTransform extends Visitor {
|
|
|
89
176
|
point.line = rightLc?.line;
|
|
90
177
|
point.column = rightLc?.column;
|
|
91
178
|
point.file = path;
|
|
92
|
-
point.type =
|
|
179
|
+
point.type = "LogicalBranch";
|
|
180
|
+
this.assignParentMetadata(point);
|
|
93
181
|
point.hash = hash(point);
|
|
94
182
|
const replacer = new RangeTransform(node);
|
|
95
183
|
const registerStmt = createRegisterStatement(point);
|
|
@@ -100,10 +188,25 @@ export class CoverageTransform extends Visitor {
|
|
|
100
188
|
this.globalStatements.push(registerStmt);
|
|
101
189
|
break;
|
|
102
190
|
}
|
|
191
|
+
default: {
|
|
192
|
+
if (!this.isAssignmentOperator(node.operator))
|
|
193
|
+
break;
|
|
194
|
+
const right = node.right;
|
|
195
|
+
if (isBuiltinCallExpression(right))
|
|
196
|
+
break;
|
|
197
|
+
const point = this.createPoint(path, right, "Assignment");
|
|
198
|
+
const replacer = new RangeTransform(node);
|
|
199
|
+
const registerStmt = createRegisterStatement(point);
|
|
200
|
+
replacer.visit(registerStmt);
|
|
201
|
+
const coverExpression = createCoverExpression(point.hash, right, node);
|
|
202
|
+
replacer.visit(coverExpression);
|
|
203
|
+
node.right = coverExpression;
|
|
204
|
+
this.globalStatements.push(registerStmt);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
103
207
|
}
|
|
104
208
|
}
|
|
105
209
|
visitMethodDeclaration(node) {
|
|
106
|
-
super.visitMethodDeclaration(node);
|
|
107
210
|
if (node.visited)
|
|
108
211
|
return;
|
|
109
212
|
node.visited = true;
|
|
@@ -112,13 +215,9 @@ export class CoverageTransform extends Visitor {
|
|
|
112
215
|
return;
|
|
113
216
|
node.body.visited = true;
|
|
114
217
|
const path = node.range.source.normalizedPath;
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
point
|
|
118
|
-
point.column = funcLc?.column;
|
|
119
|
-
point.file = path;
|
|
120
|
-
point.type = CoverType.Function;
|
|
121
|
-
point.hash = hash(point);
|
|
218
|
+
const methodName = getNodeName(node.name);
|
|
219
|
+
const scopeKind = methodName == "constructor" ? "Constructor" : "Method";
|
|
220
|
+
const point = this.createFunctionPoint(path, node, scopeKind, methodName);
|
|
122
221
|
const replacer = new RangeTransform(node);
|
|
123
222
|
const registerStmt = createRegisterStatement(point);
|
|
124
223
|
replacer.visit(registerStmt);
|
|
@@ -127,6 +226,13 @@ export class CoverageTransform extends Visitor {
|
|
|
127
226
|
const bodyBlock = node.body;
|
|
128
227
|
bodyBlock.statements.unshift(coverStmt);
|
|
129
228
|
this.globalStatements.push(registerStmt);
|
|
229
|
+
this.withScope(point, () => {
|
|
230
|
+
this.visit(node.name, node);
|
|
231
|
+
this.visit(node.typeParameters, node);
|
|
232
|
+
this.visit(node.signature, node);
|
|
233
|
+
this.visit(node.decorators, node);
|
|
234
|
+
this.visit(bodyBlock.statements, bodyBlock);
|
|
235
|
+
});
|
|
130
236
|
}
|
|
131
237
|
}
|
|
132
238
|
visitParameter(node) {
|
|
@@ -146,7 +252,8 @@ export class CoverageTransform extends Visitor {
|
|
|
146
252
|
point.line = paramLc?.line;
|
|
147
253
|
point.column = paramLc?.column;
|
|
148
254
|
point.file = path;
|
|
149
|
-
point.type =
|
|
255
|
+
point.type = "DefaultValue";
|
|
256
|
+
this.assignParentMetadata(point);
|
|
150
257
|
point.hash = hash(point);
|
|
151
258
|
const replacer = new RangeTransform(node);
|
|
152
259
|
const registerStmt = createRegisterStatement(point);
|
|
@@ -158,7 +265,6 @@ export class CoverageTransform extends Visitor {
|
|
|
158
265
|
}
|
|
159
266
|
}
|
|
160
267
|
visitFunctionDeclaration(node, isDefault) {
|
|
161
|
-
super.visitFunctionDeclaration(node, isDefault);
|
|
162
268
|
if (node.visited)
|
|
163
269
|
return;
|
|
164
270
|
node.visited = true;
|
|
@@ -167,13 +273,7 @@ export class CoverageTransform extends Visitor {
|
|
|
167
273
|
return;
|
|
168
274
|
node.body.visited = true;
|
|
169
275
|
const path = node.range.source.normalizedPath;
|
|
170
|
-
const
|
|
171
|
-
const point = new CoverPoint();
|
|
172
|
-
point.line = funcLc?.line;
|
|
173
|
-
point.column = funcLc?.column;
|
|
174
|
-
point.file = path;
|
|
175
|
-
point.type = CoverType.Function;
|
|
176
|
-
point.hash = hash(point);
|
|
276
|
+
const point = this.createFunctionPoint(path, node, "Function", node.name?.text ?? null);
|
|
177
277
|
const replacer = new RangeTransform(node);
|
|
178
278
|
const registerStmt = createRegisterStatement(point);
|
|
179
279
|
replacer.visit(registerStmt);
|
|
@@ -202,6 +302,18 @@ export class CoverageTransform extends Visitor {
|
|
|
202
302
|
const bodyBlock = node.body;
|
|
203
303
|
bodyBlock.statements.unshift(coverStmt);
|
|
204
304
|
}
|
|
305
|
+
this.withScope(point, () => {
|
|
306
|
+
this.visit(node.name, node);
|
|
307
|
+
this.visit(node.decorators, node);
|
|
308
|
+
this.visit(node.typeParameters, node);
|
|
309
|
+
this.visit(node.signature, node);
|
|
310
|
+
if (node.body instanceof BlockStatement) {
|
|
311
|
+
this.visit(node.body.statements, node.body);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
this.visit(node.body, node);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
205
317
|
}
|
|
206
318
|
}
|
|
207
319
|
}
|
|
@@ -217,42 +329,14 @@ export class CoverageTransform extends Visitor {
|
|
|
217
329
|
if (ifTrue &&
|
|
218
330
|
ifTrue.kind !== 31 &&
|
|
219
331
|
!isBuiltinStatement(ifTrue)) {
|
|
220
|
-
|
|
221
|
-
const point = new CoverPoint();
|
|
222
|
-
point.line = trueLc?.line;
|
|
223
|
-
point.column = trueLc?.column;
|
|
224
|
-
point.file = path;
|
|
225
|
-
point.type = CoverType.Expression;
|
|
226
|
-
point.hash = hash(point);
|
|
227
|
-
const replacer = new RangeTransform(ifTrue);
|
|
228
|
-
const registerStmt = createRegisterStatement(point);
|
|
229
|
-
replacer.visit(registerStmt);
|
|
230
|
-
const coverStmt = Node.createBlockStatement([createCoverStatement(point.hash, ifTrue)], ifTrue.range);
|
|
231
|
-
replacer.visit(coverStmt);
|
|
232
|
-
coverStmt.statements.push(ifTrue);
|
|
233
|
-
node.ifTrue = coverStmt;
|
|
234
|
-
this.globalStatements.push(registerStmt);
|
|
332
|
+
node.ifTrue = this.instrumentStatementBody(path, ifTrue, "IfBranch");
|
|
235
333
|
visitIfTrue = true;
|
|
236
334
|
visitIfFalse = !!ifFalse;
|
|
237
335
|
}
|
|
238
336
|
if (ifFalse &&
|
|
239
337
|
ifFalse.kind !== 31 &&
|
|
240
338
|
!isBuiltinStatement(ifFalse)) {
|
|
241
|
-
|
|
242
|
-
const point = new CoverPoint();
|
|
243
|
-
point.line = falseLc?.line;
|
|
244
|
-
point.column = falseLc?.column;
|
|
245
|
-
point.file = path;
|
|
246
|
-
point.type = CoverType.Expression;
|
|
247
|
-
point.hash = hash(point);
|
|
248
|
-
const replacer = new RangeTransform(ifFalse);
|
|
249
|
-
const registerStmt = createRegisterStatement(point);
|
|
250
|
-
replacer.visit(registerStmt);
|
|
251
|
-
const coverStmt = Node.createBlockStatement([createCoverStatement(point.hash, ifFalse)], ifFalse.range);
|
|
252
|
-
replacer.visit(coverStmt);
|
|
253
|
-
coverStmt.statements.push(ifFalse);
|
|
254
|
-
node.ifFalse = coverStmt;
|
|
255
|
-
this.globalStatements.push(registerStmt);
|
|
339
|
+
node.ifFalse = this.instrumentStatementBody(path, ifFalse, "IfBranch");
|
|
256
340
|
visitIfTrue = true;
|
|
257
341
|
visitIfFalse = true;
|
|
258
342
|
}
|
|
@@ -289,7 +373,8 @@ export class CoverageTransform extends Visitor {
|
|
|
289
373
|
point.line = trueLc?.line;
|
|
290
374
|
point.column = trueLc?.column;
|
|
291
375
|
point.file = path;
|
|
292
|
-
point.type =
|
|
376
|
+
point.type = "Ternary";
|
|
377
|
+
this.assignParentMetadata(point);
|
|
293
378
|
point.hash = hash(point);
|
|
294
379
|
const replacer = new RangeTransform(trueExpression);
|
|
295
380
|
const registerStmt = createRegisterStatement(point);
|
|
@@ -307,7 +392,8 @@ export class CoverageTransform extends Visitor {
|
|
|
307
392
|
point.line = falseLc?.line;
|
|
308
393
|
point.column = falseLc?.column;
|
|
309
394
|
point.file = path;
|
|
310
|
-
point.type =
|
|
395
|
+
point.type = "Ternary";
|
|
396
|
+
this.assignParentMetadata(point);
|
|
311
397
|
point.hash = hash(point);
|
|
312
398
|
const replacer = new RangeTransform(falseExpression);
|
|
313
399
|
const registerStmt = createRegisterStatement(point);
|
|
@@ -319,6 +405,72 @@ export class CoverageTransform extends Visitor {
|
|
|
319
405
|
}
|
|
320
406
|
}
|
|
321
407
|
}
|
|
408
|
+
visitForStatement(node) {
|
|
409
|
+
if (node.visited)
|
|
410
|
+
return;
|
|
411
|
+
node.visited = true;
|
|
412
|
+
const path = node.range.source.normalizedPath;
|
|
413
|
+
node.body = this.instrumentStatementBody(path, node.body, "Loop");
|
|
414
|
+
super.visitForStatement(node);
|
|
415
|
+
}
|
|
416
|
+
visitForOfStatement(node) {
|
|
417
|
+
if (node.visited)
|
|
418
|
+
return;
|
|
419
|
+
node.visited = true;
|
|
420
|
+
const path = node.range.source.normalizedPath;
|
|
421
|
+
node.body = this.instrumentStatementBody(path, node.body, "Loop");
|
|
422
|
+
super.visitForOfStatement(node);
|
|
423
|
+
}
|
|
424
|
+
visitWhileStatement(node) {
|
|
425
|
+
if (node.visited)
|
|
426
|
+
return;
|
|
427
|
+
node.visited = true;
|
|
428
|
+
const path = node.range.source.normalizedPath;
|
|
429
|
+
node.body = this.instrumentStatementBody(path, node.body, "Loop");
|
|
430
|
+
super.visitWhileStatement(node);
|
|
431
|
+
}
|
|
432
|
+
visitDoStatement(node) {
|
|
433
|
+
if (node.visited)
|
|
434
|
+
return;
|
|
435
|
+
node.visited = true;
|
|
436
|
+
const path = node.range.source.normalizedPath;
|
|
437
|
+
node.body = this.instrumentStatementBody(path, node.body, "Loop");
|
|
438
|
+
super.visitDoStatement(node);
|
|
439
|
+
}
|
|
440
|
+
visitReturnStatement(node) {
|
|
441
|
+
if (node.visited)
|
|
442
|
+
return;
|
|
443
|
+
node.visited = true;
|
|
444
|
+
super.visitReturnStatement(node);
|
|
445
|
+
if (!node.value || isBuiltinCallExpression(node.value))
|
|
446
|
+
return;
|
|
447
|
+
const path = node.range.source.normalizedPath;
|
|
448
|
+
const point = this.createPoint(path, node.value, "Return");
|
|
449
|
+
const replacer = new RangeTransform(node);
|
|
450
|
+
const registerStmt = createRegisterStatement(point);
|
|
451
|
+
replacer.visit(registerStmt);
|
|
452
|
+
const coverExpression = createCoverExpression(point.hash, node.value, node);
|
|
453
|
+
replacer.visit(coverExpression);
|
|
454
|
+
node.value = coverExpression;
|
|
455
|
+
this.globalStatements.push(registerStmt);
|
|
456
|
+
}
|
|
457
|
+
visitThrowStatement(node) {
|
|
458
|
+
if (node.visited)
|
|
459
|
+
return;
|
|
460
|
+
node.visited = true;
|
|
461
|
+
super.visitThrowStatement(node);
|
|
462
|
+
if (!node.value || isBuiltinCallExpression(node.value))
|
|
463
|
+
return;
|
|
464
|
+
const path = node.range.source.normalizedPath;
|
|
465
|
+
const point = this.createPoint(path, node.value, "Throw");
|
|
466
|
+
const replacer = new RangeTransform(node);
|
|
467
|
+
const registerStmt = createRegisterStatement(point);
|
|
468
|
+
replacer.visit(registerStmt);
|
|
469
|
+
const coverExpression = createCoverExpression(point.hash, node.value, node);
|
|
470
|
+
replacer.visit(coverExpression);
|
|
471
|
+
node.value = coverExpression;
|
|
472
|
+
this.globalStatements.push(registerStmt);
|
|
473
|
+
}
|
|
322
474
|
visitSwitchCase(node) {
|
|
323
475
|
if (node.visited)
|
|
324
476
|
return;
|
|
@@ -329,7 +481,8 @@ export class CoverageTransform extends Visitor {
|
|
|
329
481
|
point.line = caseLc?.line;
|
|
330
482
|
point.column = caseLc?.column;
|
|
331
483
|
point.file = path;
|
|
332
|
-
point.type =
|
|
484
|
+
point.type = "SwitchCase";
|
|
485
|
+
this.assignParentMetadata(point);
|
|
333
486
|
point.hash = hash(point);
|
|
334
487
|
const replacer = new RangeTransform(node);
|
|
335
488
|
const registerStmt = createRegisterStatement(point);
|
|
@@ -354,7 +507,8 @@ export class CoverageTransform extends Visitor {
|
|
|
354
507
|
point.line = blockLc?.line;
|
|
355
508
|
point.column = blockLc?.column;
|
|
356
509
|
point.file = path;
|
|
357
|
-
point.type =
|
|
510
|
+
point.type = "Block";
|
|
511
|
+
this.assignParentMetadata(point);
|
|
358
512
|
point.hash = hash(point);
|
|
359
513
|
const replacer = new RangeTransform(node);
|
|
360
514
|
const registerStmt = createRegisterStatement(point);
|
|
@@ -413,6 +567,11 @@ function getExpressionName(node) {
|
|
|
413
567
|
return null;
|
|
414
568
|
}
|
|
415
569
|
}
|
|
570
|
+
function getNodeName(node) {
|
|
571
|
+
if (!node)
|
|
572
|
+
return null;
|
|
573
|
+
return getExpressionName(node);
|
|
574
|
+
}
|
|
416
575
|
function djb2Hash(str) {
|
|
417
576
|
const points = Array.from(str);
|
|
418
577
|
let h = 5381;
|
|
@@ -444,7 +603,7 @@ function getLineCol(node) {
|
|
|
444
603
|
};
|
|
445
604
|
}
|
|
446
605
|
function createRegisterStatement(point) {
|
|
447
|
-
return SimpleParser.parseTopLevelStatement(`__REGISTER_RAW(${asStringLiteral(point.file)}, ${asStringLiteral(point.hash)}, ${point.line}, ${point.column}, ${asStringLiteral(
|
|
606
|
+
return SimpleParser.parseTopLevelStatement(`__REGISTER_RAW(${asStringLiteral(point.file)}, ${asStringLiteral(point.hash)}, ${point.line}, ${point.column}, ${asStringLiteral(point.type)}, ${asStringLiteral(point.parentHash)}, ${asStringLiteral(point.scopeKind)}, ${asStringLiteral(point.scopeName)}, ${point.depth})`);
|
|
448
607
|
}
|
|
449
608
|
function createCoverStatement(hashValue, ref) {
|
|
450
609
|
return Node.createExpressionStatement(createCoverCallExpression(hashValue, ref));
|
|
@@ -455,16 +614,6 @@ function createCoverExpression(hashValue, replacement, ref) {
|
|
|
455
614
|
function createCoverCallExpression(hashValue, ref) {
|
|
456
615
|
return Node.createCallExpression(Node.createIdentifierExpression("__COVER", ref.range), null, [Node.createStringLiteralExpression(hashValue, ref.range)], ref.range);
|
|
457
616
|
}
|
|
458
|
-
function coverTypeToString(type) {
|
|
459
|
-
switch (type) {
|
|
460
|
-
case CoverType.Function:
|
|
461
|
-
return "Function";
|
|
462
|
-
case CoverType.Expression:
|
|
463
|
-
return "Expression";
|
|
464
|
-
default:
|
|
465
|
-
return "Block";
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
617
|
function asStringLiteral(value) {
|
|
469
618
|
return JSON.stringify(value);
|
|
470
619
|
}
|