as-test 1.0.6 → 1.0.9
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 +13 -0
- package/README.md +45 -0
- package/as-test.config.schema.json +39 -0
- package/assembly/__fuzz__/string.fuzz.ts +1 -1
- package/assembly/as-test.intellisense.d.ts +60 -0
- package/assembly/index.ts +32 -3
- package/assembly/src/fuzz.ts +55 -14
- package/assembly/util/wipc.ts +14 -4
- package/bin/commands/fuzz-core.js +30 -2
- package/bin/commands/init-core.js +129 -19
- package/bin/commands/run-core.js +215 -21
- package/bin/commands/web-runner-source.js +86 -13
- package/bin/coverage-points.js +173 -0
- package/bin/index.js +62 -4
- package/bin/reporters/default.js +103 -4
- package/bin/types.js +9 -0
- package/bin/util.js +16 -0
- package/package.json +2 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
const sourceLineCache = new Map();
|
|
4
|
+
export function describeCoveragePoint(file, line, column, fallbackType) {
|
|
5
|
+
const context = getCoverageSourceContext(file, line, column);
|
|
6
|
+
if (!context) {
|
|
7
|
+
return {
|
|
8
|
+
displayType: fallbackType,
|
|
9
|
+
subjectName: null,
|
|
10
|
+
visible: "",
|
|
11
|
+
focus: 0,
|
|
12
|
+
highlightStart: 0,
|
|
13
|
+
highlightEnd: 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const declaration = detectCoverageDeclaration(context.visible);
|
|
17
|
+
if (declaration) {
|
|
18
|
+
const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(context.visible, context.focus);
|
|
19
|
+
return {
|
|
20
|
+
displayType: declaration.type,
|
|
21
|
+
subjectName: declaration.name,
|
|
22
|
+
visible: context.visible,
|
|
23
|
+
focus: context.focus,
|
|
24
|
+
highlightStart,
|
|
25
|
+
highlightEnd,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const call = detectCoverageCall(context.visible, context.focus);
|
|
29
|
+
if (call) {
|
|
30
|
+
return {
|
|
31
|
+
displayType: "Call",
|
|
32
|
+
subjectName: call.name,
|
|
33
|
+
visible: context.visible,
|
|
34
|
+
focus: context.focus,
|
|
35
|
+
highlightStart: call.start,
|
|
36
|
+
highlightEnd: call.end,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(context.visible, context.focus);
|
|
40
|
+
return {
|
|
41
|
+
displayType: fallbackType,
|
|
42
|
+
subjectName: null,
|
|
43
|
+
visible: context.visible,
|
|
44
|
+
focus: context.focus,
|
|
45
|
+
highlightStart,
|
|
46
|
+
highlightEnd,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function readCoverageSourceLine(file, line) {
|
|
50
|
+
const resolved = path.resolve(process.cwd(), file);
|
|
51
|
+
let lines = sourceLineCache.get(resolved);
|
|
52
|
+
if (lines === undefined) {
|
|
53
|
+
try {
|
|
54
|
+
lines = readFileSync(resolved, "utf8").split(/\r?\n/);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
lines = null;
|
|
58
|
+
}
|
|
59
|
+
sourceLineCache.set(resolved, lines);
|
|
60
|
+
}
|
|
61
|
+
if (!lines)
|
|
62
|
+
return "";
|
|
63
|
+
return lines[line - 1] ?? "";
|
|
64
|
+
}
|
|
65
|
+
export function resolveCoverageHighlightSpan(visible, focus) {
|
|
66
|
+
if (!visible.length)
|
|
67
|
+
return [0, 0];
|
|
68
|
+
const index = Math.max(0, Math.min(visible.length - 1, focus));
|
|
69
|
+
if (isCoverageBoundary(visible.charAt(index))) {
|
|
70
|
+
return [index, Math.min(visible.length, index + 1)];
|
|
71
|
+
}
|
|
72
|
+
let start = index;
|
|
73
|
+
let end = index + 1;
|
|
74
|
+
while (start > 0 && !isCoverageBoundary(visible.charAt(start - 1)))
|
|
75
|
+
start--;
|
|
76
|
+
while (end < visible.length && !isCoverageBoundary(visible.charAt(end)))
|
|
77
|
+
end++;
|
|
78
|
+
return [start, end];
|
|
79
|
+
}
|
|
80
|
+
function getCoverageSourceContext(file, line, column) {
|
|
81
|
+
const sourceLine = readCoverageSourceLine(file, line);
|
|
82
|
+
if (!sourceLine)
|
|
83
|
+
return null;
|
|
84
|
+
const expanded = sourceLine.replace(/\t/g, " ");
|
|
85
|
+
const firstNonWhitespace = expanded.search(/\S/);
|
|
86
|
+
if (firstNonWhitespace == -1)
|
|
87
|
+
return null;
|
|
88
|
+
const visible = expanded.slice(firstNonWhitespace).trimEnd();
|
|
89
|
+
if (!visible.length)
|
|
90
|
+
return null;
|
|
91
|
+
const focus = Math.max(0, Math.min(visible.length - 1, Math.max(0, column - 1 - firstNonWhitespace)));
|
|
92
|
+
return { visible, focus };
|
|
93
|
+
}
|
|
94
|
+
function detectCoverageDeclaration(visible) {
|
|
95
|
+
const trimmed = visible.trim();
|
|
96
|
+
if (!trimmed.length)
|
|
97
|
+
return null;
|
|
98
|
+
let match = trimmed.match(/^(?:export\s+)?function\s+([A-Za-z_]\w*)(?:<[^>]+>)?\s*\(/);
|
|
99
|
+
if (match)
|
|
100
|
+
return { type: "Function", name: match[1] ?? null };
|
|
101
|
+
if (trimmed.startsWith("constructor(") ||
|
|
102
|
+
/^(?:public\s+|private\s+|protected\s+)constructor\s*\(/.test(trimmed)) {
|
|
103
|
+
return { type: "Constructor", name: "constructor" };
|
|
104
|
+
}
|
|
105
|
+
match = trimmed.match(/^(?:export\s+)?(?:public\s+|private\s+|protected\s+)?(?:static\s+)?([A-Za-z_]\w*)(?:<[^>]+>)?\([^)]*\)\s*:\s*[^{=]+[{]?$/);
|
|
106
|
+
if (match)
|
|
107
|
+
return { type: "Method", name: match[1] ?? null };
|
|
108
|
+
match = trimmed.match(/^(?:public\s+|private\s+|protected\s+)?(?:readonly\s+)?([A-Za-z_]\w*)(?:<[^>]+>)?\s*:\s*[^=;{]+(?:=.*)?;?$/);
|
|
109
|
+
if (match)
|
|
110
|
+
return { type: "Property", name: match[1] ?? null };
|
|
111
|
+
if (/^(?:export\s+)?class\b/.test(trimmed)) {
|
|
112
|
+
match = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_]\w*)/);
|
|
113
|
+
return { type: "Class", name: match?.[1] ?? null };
|
|
114
|
+
}
|
|
115
|
+
if (/^(?:export\s+)?enum\b/.test(trimmed)) {
|
|
116
|
+
match = trimmed.match(/^(?:export\s+)?enum\s+([A-Za-z_]\w*)/);
|
|
117
|
+
return { type: "Enum", name: match?.[1] ?? null };
|
|
118
|
+
}
|
|
119
|
+
if (/^(?:export\s+)?interface\b/.test(trimmed)) {
|
|
120
|
+
match = trimmed.match(/^(?:export\s+)?interface\s+([A-Za-z_]\w*)/);
|
|
121
|
+
return { type: "Interface", name: match?.[1] ?? null };
|
|
122
|
+
}
|
|
123
|
+
if (/^(?:export\s+)?namespace\b/.test(trimmed)) {
|
|
124
|
+
match = trimmed.match(/^(?:export\s+)?namespace\s+([A-Za-z_]\w*)/);
|
|
125
|
+
return { type: "Namespace", name: match?.[1] ?? null };
|
|
126
|
+
}
|
|
127
|
+
if (/^(?:const|let|var)\b/.test(trimmed)) {
|
|
128
|
+
match = trimmed.match(/^(?:const|let|var)\s+([A-Za-z_]\w*)/);
|
|
129
|
+
return { type: "Variable", name: match?.[1] ?? null };
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function detectCoverageCall(visible, focus) {
|
|
134
|
+
const matches = [...visible.matchAll(/\b([A-Za-z_]\w*)(?:<[^>()]+>)?\s*\(/g)];
|
|
135
|
+
if (!matches.length)
|
|
136
|
+
return null;
|
|
137
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
138
|
+
let bestMatch = null;
|
|
139
|
+
for (const match of matches) {
|
|
140
|
+
const start = match.index ?? -1;
|
|
141
|
+
if (start == -1)
|
|
142
|
+
continue;
|
|
143
|
+
const end = start + match[0].length;
|
|
144
|
+
const distance = focus < start ? start - focus : focus >= end ? focus - end + 1 : 0;
|
|
145
|
+
if (distance < bestDistance) {
|
|
146
|
+
bestDistance = distance;
|
|
147
|
+
bestMatch = match;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!bestMatch)
|
|
151
|
+
return null;
|
|
152
|
+
const name = bestMatch[1] ?? null;
|
|
153
|
+
if (name == "if" ||
|
|
154
|
+
name == "for" ||
|
|
155
|
+
name == "while" ||
|
|
156
|
+
name == "switch" ||
|
|
157
|
+
name == "return" ||
|
|
158
|
+
name == "function") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (bestDistance > Math.max(12, Math.floor(visible.length / 3))) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const start = bestMatch.index ?? 0;
|
|
165
|
+
return {
|
|
166
|
+
name,
|
|
167
|
+
start,
|
|
168
|
+
end: start + (name?.length ?? 1),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function isCoverageBoundary(ch) {
|
|
172
|
+
return /[\s()[\]{}.,;:+\-*/%&|^!?=<>]/.test(ch);
|
|
173
|
+
}
|
package/bin/index.js
CHANGED
|
@@ -281,7 +281,7 @@ function printCommandHelp(command) {
|
|
|
281
281
|
process.stdout.write(" --enable <feature> Enable feature (coverage|try-as)\n");
|
|
282
282
|
process.stdout.write(" --disable <feature> Disable feature (coverage|try-as)\n");
|
|
283
283
|
process.stdout.write(" --fuzz Run fuzz targets after the normal test pass\n");
|
|
284
|
-
process.stdout.write(" --fuzz-runs <
|
|
284
|
+
process.stdout.write(" --fuzz-runs <value> Override fuzz iteration count, e.g. 500, 1.5x, +10%, +100000\n");
|
|
285
285
|
process.stdout.write(" --fuzz-seed <n> Override fuzz seed for this run\n");
|
|
286
286
|
process.stdout.write(" --parallel Run files through an ordered worker pool using an automatic worker count\n");
|
|
287
287
|
process.stdout.write(" --jobs <n> Run files through an ordered worker pool\n");
|
|
@@ -302,7 +302,7 @@ function printCommandHelp(command) {
|
|
|
302
302
|
process.stdout.write(chalk.bold("Flags:\n"));
|
|
303
303
|
process.stdout.write(" --config <path> Use a specific config file\n");
|
|
304
304
|
process.stdout.write(" --mode <name[,name...]> Run one or multiple named config modes\n");
|
|
305
|
-
process.stdout.write(" --runs <
|
|
305
|
+
process.stdout.write(" --runs <value> Override fuzz iteration count, e.g. 500, 1.5x, +10%, +100000\n");
|
|
306
306
|
process.stdout.write(" --seed <n> Override fuzz seed\n");
|
|
307
307
|
process.stdout.write(" --jobs <n> Run files through an ordered worker pool\n");
|
|
308
308
|
process.stdout.write(" --build-jobs <n> Limit concurrent build tasks (defaults to --jobs)\n");
|
|
@@ -482,9 +482,10 @@ function resolveFuzzOverrides(rawArgs, command) {
|
|
|
482
482
|
runs: "--fuzz-runs",
|
|
483
483
|
seed: "--fuzz-seed",
|
|
484
484
|
};
|
|
485
|
-
const runs =
|
|
485
|
+
const runs = parseFuzzRunsFlag(rawArgs, i, direct.runs);
|
|
486
486
|
if (runs) {
|
|
487
|
-
out.runs = runs.
|
|
487
|
+
out.runs = runs.absoluteRuns;
|
|
488
|
+
out.runsOverride = runs.override;
|
|
488
489
|
if (runs.consumeNext)
|
|
489
490
|
i++;
|
|
490
491
|
continue;
|
|
@@ -499,6 +500,63 @@ function resolveFuzzOverrides(rawArgs, command) {
|
|
|
499
500
|
}
|
|
500
501
|
return out;
|
|
501
502
|
}
|
|
503
|
+
function parseFuzzRunsFlag(rawArgs, index, flag) {
|
|
504
|
+
const arg = rawArgs[index];
|
|
505
|
+
let value = "";
|
|
506
|
+
let consumeNext = false;
|
|
507
|
+
if (arg == flag) {
|
|
508
|
+
const next = rawArgs[index + 1];
|
|
509
|
+
if (!next || !next.length) {
|
|
510
|
+
throw new Error(`${flag} requires a value such as 500, 1.5x, +10%, or +100000`);
|
|
511
|
+
}
|
|
512
|
+
value = next;
|
|
513
|
+
consumeNext = true;
|
|
514
|
+
}
|
|
515
|
+
else if (arg.startsWith(`${flag}=`)) {
|
|
516
|
+
value = arg.slice(flag.length + 1);
|
|
517
|
+
if (!value.length) {
|
|
518
|
+
throw new Error(`${flag} requires a value such as 500, 1.5x, +10%, or +100000`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const parsed = parseFuzzRunsValue(flag, value.trim());
|
|
525
|
+
return {
|
|
526
|
+
key: flag,
|
|
527
|
+
absoluteRuns: parsed.kind == "set" ? parsed.value : undefined,
|
|
528
|
+
override: parsed,
|
|
529
|
+
consumeNext,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function parseFuzzRunsValue(flag, value) {
|
|
533
|
+
if (/^\d+$/.test(value)) {
|
|
534
|
+
const parsed = parseIntegerFlag(flag, value);
|
|
535
|
+
return { kind: "set", value: parsed };
|
|
536
|
+
}
|
|
537
|
+
if (/^[+-]\d+$/.test(value)) {
|
|
538
|
+
const delta = Number(value);
|
|
539
|
+
if (!Number.isFinite(delta) || !Number.isInteger(delta)) {
|
|
540
|
+
throw new Error(`${flag} additive run override must be an integer`);
|
|
541
|
+
}
|
|
542
|
+
return { kind: "add", value: delta };
|
|
543
|
+
}
|
|
544
|
+
if (/^\d+(?:\.\d+)?x$/i.test(value)) {
|
|
545
|
+
const factor = Number(value.slice(0, -1));
|
|
546
|
+
if (!Number.isFinite(factor) || factor <= 0) {
|
|
547
|
+
throw new Error(`${flag} multiplier must be greater than 0`);
|
|
548
|
+
}
|
|
549
|
+
return { kind: "scale", value: factor };
|
|
550
|
+
}
|
|
551
|
+
if (/^[+-]\d+(?:\.\d+)?%$/.test(value)) {
|
|
552
|
+
const percent = Number(value.slice(0, -1));
|
|
553
|
+
if (!Number.isFinite(percent)) {
|
|
554
|
+
throw new Error(`${flag} percentage must be numeric`);
|
|
555
|
+
}
|
|
556
|
+
return { kind: "percent-add", value: percent };
|
|
557
|
+
}
|
|
558
|
+
throw new Error(`${flag} must be a run count or expression such as 500, 1.5x, +10%, or +100000`);
|
|
559
|
+
}
|
|
502
560
|
function resolveListFlags(rawArgs, command) {
|
|
503
561
|
const out = {
|
|
504
562
|
list: false,
|
package/bin/reporters/default.js
CHANGED
|
@@ -3,6 +3,7 @@ import { diff } from "typer-diff";
|
|
|
3
3
|
import { readFileSync } from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { formatTime } from "../util.js";
|
|
6
|
+
import { describeCoveragePoint, readCoverageSourceLine, resolveCoverageHighlightSpan, } from "../coverage-points.js";
|
|
6
7
|
export const createReporter = (context) => {
|
|
7
8
|
return new DefaultReporter(context);
|
|
8
9
|
};
|
|
@@ -633,21 +634,60 @@ function renderSummaryLine(label, summary, layout = {
|
|
|
633
634
|
process.stdout.write(totalText.padStart(layout.totalWidth) + "\n");
|
|
634
635
|
}
|
|
635
636
|
function renderCoverageSummary(summary) {
|
|
637
|
+
console.log("");
|
|
638
|
+
console.log(chalk.bold("Coverage"));
|
|
639
|
+
if (!summary.files.length || summary.total <= 0) {
|
|
640
|
+
console.log(` ${chalk.dim("No eligible source files were tracked for coverage.")}`);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
636
643
|
const pct = summary.total
|
|
637
644
|
? ((summary.covered * 100) / summary.total).toFixed(2)
|
|
638
645
|
: "100.00";
|
|
646
|
+
const missingLabel = summary.uncovered == 1 ? "1 point missing" : `${summary.uncovered} points missing`;
|
|
647
|
+
const fileLabel = summary.files.length == 1 ? "1 file" : `${summary.files.length} files`;
|
|
639
648
|
const color = Number(pct) >= 90
|
|
640
649
|
? chalk.greenBright
|
|
641
650
|
: Number(pct) >= 75
|
|
642
651
|
? chalk.yellowBright
|
|
643
652
|
: chalk.redBright;
|
|
644
|
-
console.log("");
|
|
645
|
-
|
|
653
|
+
console.log(` ${color(pct + "%")} ${renderCoverageBar(summary.percent)} ${chalk.dim(`(${summary.covered}/${summary.total} covered, ${missingLabel}, ${fileLabel})`)}`);
|
|
654
|
+
const ranked = [...summary.files].sort((a, b) => {
|
|
655
|
+
if (a.percent != b.percent)
|
|
656
|
+
return a.percent - b.percent;
|
|
657
|
+
if (a.uncovered != b.uncovered)
|
|
658
|
+
return b.uncovered - a.uncovered;
|
|
659
|
+
return a.file.localeCompare(b.file);
|
|
660
|
+
});
|
|
661
|
+
console.log(chalk.bold(" File Breakdown"));
|
|
662
|
+
for (const file of ranked.slice(0, 8)) {
|
|
663
|
+
const filePct = file.total
|
|
664
|
+
? ((file.covered * 100) / file.total).toFixed(2)
|
|
665
|
+
: "100.00";
|
|
666
|
+
const fileColor = Number(filePct) >= 90
|
|
667
|
+
? chalk.greenBright
|
|
668
|
+
: Number(filePct) >= 75
|
|
669
|
+
? chalk.yellowBright
|
|
670
|
+
: chalk.redBright;
|
|
671
|
+
const suffix = file.uncovered > 0
|
|
672
|
+
? `${file.uncovered} missing`
|
|
673
|
+
: "fully covered";
|
|
674
|
+
console.log(` ${fileColor(filePct.padStart(6) + "%")} ${toRelativeResultPath(file.file).padEnd(36)} ${chalk.dim(`${file.covered}/${file.total} covered, ${suffix}`)}`);
|
|
675
|
+
}
|
|
676
|
+
if (ranked.length > 8) {
|
|
677
|
+
console.log(chalk.dim(` ... ${ranked.length - 8} more files`));
|
|
678
|
+
}
|
|
646
679
|
}
|
|
647
680
|
function renderCoveragePoints(files) {
|
|
648
681
|
console.log("");
|
|
649
|
-
console.log(chalk.bold("Coverage
|
|
682
|
+
console.log(chalk.bold("Coverage Gaps"));
|
|
650
683
|
const sortedFiles = [...files].sort((a, b) => a.file.localeCompare(b.file));
|
|
684
|
+
const missingPoints = sortedFiles.flatMap((file) => file.points
|
|
685
|
+
.filter((point) => !point.executed)
|
|
686
|
+
.map((point) => ({
|
|
687
|
+
...point,
|
|
688
|
+
displayType: describeCoveragePoint(point.file, point.line, point.column, point.type).displayType,
|
|
689
|
+
})));
|
|
690
|
+
const layout = createCoverageGapLayout(missingPoints);
|
|
651
691
|
for (const file of sortedFiles) {
|
|
652
692
|
const points = [...file.points].sort((a, b) => {
|
|
653
693
|
if (a.line != b.line)
|
|
@@ -656,10 +696,69 @@ function renderCoveragePoints(files) {
|
|
|
656
696
|
return a.column - b.column;
|
|
657
697
|
return a.type.localeCompare(b.type);
|
|
658
698
|
});
|
|
699
|
+
const missing = points.filter((point) => !point.executed);
|
|
700
|
+
if (!missing.length)
|
|
701
|
+
continue;
|
|
702
|
+
console.log(` ${chalk.bold(toRelativeResultPath(file.file))} ${chalk.dim(`(${missing.length} uncovered)`)}`);
|
|
659
703
|
for (const point of points) {
|
|
660
704
|
if (point.executed)
|
|
661
705
|
continue;
|
|
662
|
-
|
|
706
|
+
const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
|
|
707
|
+
const snippet = formatCoverageSnippet(point.file, point.line, point.column);
|
|
708
|
+
const typeLabel = describeCoveragePoint(point.file, point.line, point.column, point.type).displayType.padEnd(layout.typeWidth + 4);
|
|
709
|
+
const locationLabel = location.padEnd(layout.locationWidth + 4);
|
|
710
|
+
console.log(` ${chalk.red("x")} ${chalk.dim(typeLabel)}${chalk.dim(locationLabel)}${snippet}`);
|
|
663
711
|
}
|
|
664
712
|
}
|
|
665
713
|
}
|
|
714
|
+
function renderCoverageBar(percent) {
|
|
715
|
+
const slots = 12;
|
|
716
|
+
const filled = Math.max(0, Math.min(slots, Math.round((Math.max(0, Math.min(100, percent)) / 100) * slots)));
|
|
717
|
+
return `[${"=".repeat(filled)}${"-".repeat(slots - filled)}]`;
|
|
718
|
+
}
|
|
719
|
+
function createCoverageGapLayout(points) {
|
|
720
|
+
return {
|
|
721
|
+
typeWidth: Math.max(...points.map((point) => point.displayType.length), 5),
|
|
722
|
+
locationWidth: Math.max(...points.map((point) => `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`.length), 1),
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function formatCoverageSnippet(file, line, column) {
|
|
726
|
+
const sourceLine = readCoverageSourceLine(file, line);
|
|
727
|
+
if (!sourceLine)
|
|
728
|
+
return "";
|
|
729
|
+
const expanded = sourceLine.replace(/\t/g, " ");
|
|
730
|
+
const firstNonWhitespace = expanded.search(/\S/);
|
|
731
|
+
if (firstNonWhitespace == -1)
|
|
732
|
+
return "";
|
|
733
|
+
const visible = expanded.slice(firstNonWhitespace).trimEnd();
|
|
734
|
+
if (!visible.length)
|
|
735
|
+
return "";
|
|
736
|
+
const maxWidth = 72;
|
|
737
|
+
const focus = Math.max(0, Math.min(visible.length - 1, Math.max(0, column - 1 - firstNonWhitespace)));
|
|
738
|
+
if (visible.length <= maxWidth) {
|
|
739
|
+
return styleCoverageSnippetWindow(visible, 0, visible.length, focus);
|
|
740
|
+
}
|
|
741
|
+
const start = Math.max(0, Math.min(visible.length - maxWidth, focus - Math.floor(maxWidth / 2)));
|
|
742
|
+
const end = Math.min(visible.length, start + maxWidth);
|
|
743
|
+
return styleCoverageSnippetWindow(visible, start, end, focus);
|
|
744
|
+
}
|
|
745
|
+
function styleCoverageSnippetWindow(visible, start, end, focus) {
|
|
746
|
+
const prefix = start > 0 ? "..." : "";
|
|
747
|
+
const suffix = end < visible.length ? "..." : "";
|
|
748
|
+
const slice = visible.slice(start, end);
|
|
749
|
+
const localFocus = Math.max(0, Math.min(slice.length - 1, focus - start));
|
|
750
|
+
const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(visible, focus);
|
|
751
|
+
const localStart = Math.max(0, Math.min(slice.length, highlightStart - start));
|
|
752
|
+
const localEnd = Math.max(localStart + 1, Math.min(slice.length, highlightEnd - start));
|
|
753
|
+
if (!slice.length)
|
|
754
|
+
return "";
|
|
755
|
+
if (localStart >= slice.length) {
|
|
756
|
+
return chalk.dim(`${prefix}${slice}${suffix}`);
|
|
757
|
+
}
|
|
758
|
+
const head = slice.slice(0, localStart);
|
|
759
|
+
const body = slice.slice(localStart, localEnd || localStart + 1);
|
|
760
|
+
const tail = slice.slice(localEnd || localStart + 1);
|
|
761
|
+
return (chalk.dim(prefix + head) +
|
|
762
|
+
chalk.dim.underline(body.length ? body : slice.charAt(localFocus)) +
|
|
763
|
+
chalk.dim(tail + suffix));
|
|
764
|
+
}
|
package/bin/types.js
CHANGED
|
@@ -21,6 +21,15 @@ export class CoverageOptions {
|
|
|
21
21
|
this.includeSpecs = false;
|
|
22
22
|
this.include = [];
|
|
23
23
|
this.exclude = [];
|
|
24
|
+
this.ignore = new CoverageIgnoreOptions();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class CoverageIgnoreOptions {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.labels = [];
|
|
30
|
+
this.names = [];
|
|
31
|
+
this.locations = [];
|
|
32
|
+
this.snippets = [];
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
export class Suite {
|
package/bin/util.js
CHANGED
|
@@ -340,6 +340,22 @@ function validateCoverageValue(value, path, issues) {
|
|
|
340
340
|
}
|
|
341
341
|
validateStringArrayField(obj, "include", path, issues);
|
|
342
342
|
validateStringArrayField(obj, "exclude", path, issues);
|
|
343
|
+
if ("ignore" in obj && obj.ignore != undefined) {
|
|
344
|
+
if (!obj.ignore || typeof obj.ignore != "object" || Array.isArray(obj.ignore)) {
|
|
345
|
+
issues.push({
|
|
346
|
+
path: `${path}.ignore`,
|
|
347
|
+
message: "must be an object",
|
|
348
|
+
fix: 'set "ignore" to an object such as { "labels": ["Call"], "names": ["panic"] }',
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const ignore = obj.ignore;
|
|
353
|
+
validateStringArrayField(ignore, "labels", `${path}.ignore`, issues);
|
|
354
|
+
validateStringArrayField(ignore, "names", `${path}.ignore`, issues);
|
|
355
|
+
validateStringArrayField(ignore, "locations", `${path}.ignore`, issues);
|
|
356
|
+
validateStringArrayField(ignore, "snippets", `${path}.ignore`, issues);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
343
359
|
}
|
|
344
360
|
function validateStringArrayField(raw, key, pathPrefix, issues) {
|
|
345
361
|
if (!(key in raw) || raw[key] == undefined)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "as-test",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"author": "Jairus Tanaka",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"description": "Testing framework for AssemblyScript. Compatible with WASI or Bindings",
|
|
40
40
|
"files": [
|
|
41
41
|
"assembly/**/*.ts",
|
|
42
|
+
"assembly/**/*.d.ts",
|
|
42
43
|
"!assembly/__tests__/**",
|
|
43
44
|
"!assembly/tsconfig.json",
|
|
44
45
|
"bin/**/*.js",
|