supercov 0.0.43 → 0.0.44
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/README.md +2 -2
- package/analyzers/typescript/README.md +4 -0
- package/analyzers/typescript/bin/identity.mjs +4 -0
- package/analyzers/typescript/dist/analyze.js +1352 -51
- package/analyzers/typescript/dist/archive.js +31 -3
- package/analyzers/typescript/dist/awaited-observations.js +376 -0
- package/analyzers/typescript/dist/build-identity.json +1 -1
- package/analyzers/typescript/dist/mock-counts.js +2517 -0
- package/analyzers/typescript/dist/pragmas.js +59 -16
- package/analyzers/typescript/src/analyze.ts +1738 -96
- package/analyzers/typescript/src/archive.ts +36 -3
- package/analyzers/typescript/src/awaited-observations.ts +561 -0
- package/analyzers/typescript/src/mock-counts.ts +3219 -0
- package/analyzers/typescript/src/pragmas.ts +90 -24
- package/docs/agent-loop.md +116 -31
- package/docs/assertion-evidence.md +560 -1
- package/docs/cli.md +15 -15
- package/docs/code-verification.md +3 -181
- package/docs/coverage-model.md +6 -6
- package/docs/evidence.md +6 -6
- package/docs/getting-started.md +60 -72
- package/docs/performance.md +4 -4
- package/docs/troubleshooting.md +8 -8
- package/docs/verification.md +2 -2
- package/docs/workspace-isolation.md +1 -1
- package/package.json +11 -9
- package/runtime/javascript/nodeAssertAdapter.mjs +32 -8
- package/runtime/javascript/nodeTest.mjs +13 -5
- package/runtime/javascript/runnerEvidence.mjs +33 -11
- package/runtime/javascript/runtime.mjs +22 -3
package/docs/troubleshooting.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Start with the run summary. It usually tells you whether the problem is the test
|
|
4
4
|
command, source discovery, runner attribution, or a measurement boundary.
|
|
5
5
|
|
|
6
|
-
```sh
|
|
6
|
+
```sh supercov
|
|
7
7
|
npx supercov runs latest
|
|
8
8
|
npx supercov runs latest scope
|
|
9
9
|
npx supercov runs latest runners
|
|
@@ -28,13 +28,13 @@ would for another npm package.
|
|
|
28
28
|
Run the same command from the repository root. If first-party code lives in an
|
|
29
29
|
unusual directory, declare the source roots explicitly:
|
|
30
30
|
|
|
31
|
-
```sh
|
|
31
|
+
```sh supercov
|
|
32
32
|
SUPERCOV_SOURCE_ROOTS=src,app npx supercov -- npm test
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Then inspect what Supercov included and excluded:
|
|
36
36
|
|
|
37
|
-
```sh
|
|
37
|
+
```sh supercov
|
|
38
38
|
npx supercov runs latest scope
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -45,7 +45,7 @@ warning. The goal is an honest boundary around code the repository owns.
|
|
|
45
45
|
|
|
46
46
|
First check runner and source scope:
|
|
47
47
|
|
|
48
|
-
```sh
|
|
48
|
+
```sh supercov
|
|
49
49
|
npx supercov runs latest runners
|
|
50
50
|
npx supercov runs latest scope
|
|
51
51
|
npx supercov runs latest gaps
|
|
@@ -67,7 +67,7 @@ file that cannot be compiled with them is measured through Ruby's `Coverage`
|
|
|
67
67
|
module alone rather than failing the run. To put a file on that path
|
|
68
68
|
deliberately, name a fragment of its path:
|
|
69
69
|
|
|
70
|
-
```sh
|
|
70
|
+
```sh supercov
|
|
71
71
|
SUPERCOV_RUBY_SKIP_PROBES=app/models/order.rb npx supercov -- bundle exec rspec
|
|
72
72
|
```
|
|
73
73
|
|
|
@@ -82,7 +82,7 @@ workspace after relevant source, tests, dependencies, configuration, or
|
|
|
82
82
|
toolchain inputs change. Rerun the same complete command to create a current
|
|
83
83
|
baseline:
|
|
84
84
|
|
|
85
|
-
```sh
|
|
85
|
+
```sh supercov
|
|
86
86
|
npx supercov -- npm test
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -133,7 +133,7 @@ mode still match.
|
|
|
133
133
|
|
|
134
134
|
Inspect the recorded phases with:
|
|
135
135
|
|
|
136
|
-
```sh
|
|
136
|
+
```sh supercov
|
|
137
137
|
npx supercov runs latest
|
|
138
138
|
```
|
|
139
139
|
|
|
@@ -143,7 +143,7 @@ See [Speed and storage](performance.md) for practical ways to shorten a loop.
|
|
|
143
143
|
|
|
144
144
|
Preview cleanup, then choose how much history to keep:
|
|
145
145
|
|
|
146
|
-
```sh
|
|
146
|
+
```sh supercov
|
|
147
147
|
npx supercov clean --dry-run
|
|
148
148
|
npx supercov clean --keep 20
|
|
149
149
|
npx supercov clean
|
package/docs/verification.md
CHANGED
|
@@ -16,7 +16,7 @@ Before merging, check three things:
|
|
|
16
16
|
3. **The test protects behavior.** It should make a meaningful assertion without
|
|
17
17
|
weakening existing assertions or changing application code for the metric.
|
|
18
18
|
|
|
19
|
-
```sh
|
|
19
|
+
```sh supercov
|
|
20
20
|
npx supercov diff <baseline-run> <new-run>
|
|
21
21
|
npx supercov runs <new-run> test "new test name"
|
|
22
22
|
```
|
|
@@ -34,7 +34,7 @@ Supercov separates four states:
|
|
|
34
34
|
- **stale** — the run is valid history but no longer describes the current
|
|
35
35
|
workspace.
|
|
36
36
|
|
|
37
|
-
```sh
|
|
37
|
+
```sh supercov
|
|
38
38
|
npx supercov runs latest
|
|
39
39
|
npx supercov runs latest scope
|
|
40
40
|
npx supercov runs latest gaps
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"description": "Coverage for coding agents and software factories 🌙",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"docs",
|
|
19
19
|
"analyzers/typescript/bin",
|
|
20
20
|
"analyzers/typescript/dist/analyze.js",
|
|
21
|
+
"analyzers/typescript/dist/mock-counts.js",
|
|
21
22
|
"analyzers/typescript/dist/archive.js",
|
|
23
|
+
"analyzers/typescript/dist/awaited-observations.js",
|
|
22
24
|
"analyzers/typescript/dist/compiler.js",
|
|
23
25
|
"analyzers/typescript/dist/frontend.js",
|
|
24
26
|
"analyzers/typescript/dist/native-frontend.js",
|
|
@@ -92,14 +94,14 @@
|
|
|
92
94
|
"test:rust-public-cargo": "cargo build -p supercov && node scripts/rust-public-cargo-integration.mjs"
|
|
93
95
|
},
|
|
94
96
|
"optionalDependencies": {
|
|
95
|
-
"@supercov/cli-darwin-arm64": "0.0.
|
|
96
|
-
"@supercov/cli-darwin-x64": "0.0.
|
|
97
|
-
"@supercov/cli-linux-arm64-gnu": "0.0.
|
|
98
|
-
"@supercov/cli-linux-arm64-musl": "0.0.
|
|
99
|
-
"@supercov/cli-linux-x64-gnu": "0.0.
|
|
100
|
-
"@supercov/cli-linux-x64-musl": "0.0.
|
|
101
|
-
"@supercov/cli-win32-arm64": "0.0.
|
|
102
|
-
"@supercov/cli-win32-x64": "0.0.
|
|
97
|
+
"@supercov/cli-darwin-arm64": "0.0.44",
|
|
98
|
+
"@supercov/cli-darwin-x64": "0.0.44",
|
|
99
|
+
"@supercov/cli-linux-arm64-gnu": "0.0.44",
|
|
100
|
+
"@supercov/cli-linux-arm64-musl": "0.0.44",
|
|
101
|
+
"@supercov/cli-linux-x64-gnu": "0.0.44",
|
|
102
|
+
"@supercov/cli-linux-x64-musl": "0.0.44",
|
|
103
|
+
"@supercov/cli-win32-arm64": "0.0.44",
|
|
104
|
+
"@supercov/cli-win32-x64": "0.0.44"
|
|
103
105
|
},
|
|
104
106
|
"peerDependencies": {
|
|
105
107
|
"@playwright/test": ">=1.55.0",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { withNodeAssertionPhase } from "./runtime.mjs";
|
|
2
|
+
import { relative, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
const ASSERTION_METHODS = new Set([
|
|
3
5
|
"deepEqual",
|
|
4
6
|
"deepStrictEqual",
|
|
@@ -19,18 +21,40 @@ const ASSERTION_METHODS = new Set([
|
|
|
19
21
|
"strictEqual",
|
|
20
22
|
"throws",
|
|
21
23
|
]);
|
|
22
|
-
function assertionSource() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
function assertionSource(boundary) {
|
|
25
|
+
// Omit our own frames by function identity, not basename: a user's helper
|
|
26
|
+
// may itself be called runtime.mjs or nodeAssertAdapter.mjs. Never search
|
|
27
|
+
// deeper for a plausible-looking caller when the immediate frame is opaque.
|
|
28
|
+
// A loader may have transformed this file without composing source maps.
|
|
29
|
+
// Qualify stack coordinates so they cannot masquerade as the original
|
|
30
|
+
// source positions emitted by lexical probes and used for oracle matching.
|
|
31
|
+
try {
|
|
32
|
+
const error = {};
|
|
33
|
+
Error.captureStackTrace(error, boundary);
|
|
34
|
+
if (typeof error.stack !== "string")
|
|
35
|
+
return undefined;
|
|
36
|
+
const entry = error.stack.split("\n")[1]?.trim();
|
|
37
|
+
const match = entry && /(?:^at (?:async )?| \()((?:file:\/\/\/|\/|[A-Za-z]:[\\/]).*):(\d+):(\d+)\)?$/.exec(entry);
|
|
38
|
+
if (!match)
|
|
39
|
+
return undefined;
|
|
40
|
+
const file = match[1].startsWith("file:") ? fileURLToPath(match[1]) : match[1];
|
|
41
|
+
const roots = [process.env.SUPERCOV_PROJECT_ROOT, process.env.SUPERCOV_SOURCE_PROJECT_ROOT, process.cwd()].filter(Boolean);
|
|
42
|
+
for (const root of roots) {
|
|
43
|
+
const path = relative(root, file);
|
|
44
|
+
if (path !== ".." && !path.startsWith(`..${sep}`) && !/^(?:[A-Za-z]:|[\\/])/.test(path))
|
|
45
|
+
return `runtime-stack:${path.split(sep).join("/")}:${match[2]}:${match[3]}`;
|
|
46
|
+
}
|
|
47
|
+
return `runtime-stack:${file.split(sep).join("/")}:${match[2]}:${match[3]}`;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Custom stack formatters must not change the user's assertion outcome.
|
|
27
51
|
return undefined;
|
|
28
|
-
|
|
52
|
+
}
|
|
29
53
|
}
|
|
30
54
|
function wrapAssertion(original, operation) {
|
|
31
55
|
return new Proxy(original, {
|
|
32
|
-
apply(target, thisArgument, argumentsList) {
|
|
33
|
-
return withNodeAssertionPhase(operation, assertionSource(), () => Reflect.apply(target, thisArgument, argumentsList));
|
|
56
|
+
apply: function assertionApply(target, thisArgument, argumentsList) {
|
|
57
|
+
return withNodeAssertionPhase(operation, () => assertionSource(assertionApply), () => Reflect.apply(target, thisArgument, argumentsList));
|
|
34
58
|
},
|
|
35
59
|
});
|
|
36
60
|
}
|
|
@@ -2,7 +2,7 @@ import * as native from "node:test";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import vm from "node:vm";
|
|
4
4
|
import { beginBufferedServerEvidence, flushBufferedServerEvidence, takeNodeAssertionPhases, withCoverageCarrier, } from "./runtime.mjs";
|
|
5
|
-
import { callerLocation, runnerExecutionScope, writeRunnerEvidence, } from "./runnerEvidence.mjs";
|
|
5
|
+
import { callerLocation, runnerExecutionScope, runnerTestId, writeRunnerEvidence, } from "./runnerEvidence.mjs";
|
|
6
6
|
function callbackIndex(args) {
|
|
7
7
|
for (let index = args.length - 1; index >= 0; index -= 1)
|
|
8
8
|
if (typeof args[index] === "function")
|
|
@@ -86,16 +86,18 @@ function restoreUserError(error, depth = 0) {
|
|
|
86
86
|
}
|
|
87
87
|
return error;
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
const registrationCounts = new Map();
|
|
90
|
+
function wrappedRegistration(original, parentTestId) {
|
|
90
91
|
const wrapped = function supercovNodeTest(...args) {
|
|
91
92
|
const index = callbackIndex(args);
|
|
92
93
|
if (index < 0)
|
|
93
94
|
return Reflect.apply(original, this, args);
|
|
94
95
|
const callback = args[index];
|
|
95
|
-
const location = callerLocation(
|
|
96
|
+
const location = callerLocation(supercovNodeTest);
|
|
96
97
|
const identity = {
|
|
97
98
|
runner: "node:test",
|
|
98
99
|
name: testName(args, callback),
|
|
100
|
+
...(parentTestId ? { parentTestId } : {}),
|
|
99
101
|
...location,
|
|
100
102
|
// Source-map producers disagree about whether a call expression maps to
|
|
101
103
|
// its first token or the first token on its source line. The line and
|
|
@@ -104,6 +106,12 @@ function wrappedRegistration(original) {
|
|
|
104
106
|
// keeps one identity when esbuild, Babel, SWC, or TypeScript rewrites it.
|
|
105
107
|
...(location.line === undefined ? {} : { column: 1 }),
|
|
106
108
|
};
|
|
109
|
+
// Allocate at registration, never callback completion: concurrent tests
|
|
110
|
+
// can finish in any order. Count each source/name within its parent.
|
|
111
|
+
const registrationKey = runnerTestId(identity);
|
|
112
|
+
const registrationOrdinal = registrationCounts.get(registrationKey) ?? 0;
|
|
113
|
+
registrationCounts.set(registrationKey, registrationOrdinal + 1);
|
|
114
|
+
identity.registrationOrdinal = registrationOrdinal;
|
|
107
115
|
const scope = runnerExecutionScope(identity);
|
|
108
116
|
const options = testOptions(args, index);
|
|
109
117
|
const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"];
|
|
@@ -129,7 +137,7 @@ function wrappedRegistration(original) {
|
|
|
129
137
|
};
|
|
130
138
|
}
|
|
131
139
|
if (property === "test" && typeof value === "function")
|
|
132
|
-
return wrappedRegistration(value.bind(target));
|
|
140
|
+
return wrappedRegistration(value.bind(target), scope.testId);
|
|
133
141
|
return typeof value === "function" ? value.bind(target) : value;
|
|
134
142
|
},
|
|
135
143
|
});
|
|
@@ -192,7 +200,7 @@ function wrappedRegistration(original) {
|
|
|
192
200
|
Object.defineProperty(wrapped, property, {
|
|
193
201
|
configurable: true,
|
|
194
202
|
enumerable: true,
|
|
195
|
-
value: wrappedRegistration(member),
|
|
203
|
+
value: wrappedRegistration(member, parentTestId),
|
|
196
204
|
});
|
|
197
205
|
}
|
|
198
206
|
return wrapped;
|
|
@@ -27,13 +27,24 @@ function localFile(file) {
|
|
|
27
27
|
return relative(process.cwd(), absolute).split(sep).join("/");
|
|
28
28
|
}
|
|
29
29
|
export function runnerTestId(identity) {
|
|
30
|
-
const
|
|
30
|
+
const parts = [
|
|
31
31
|
identity.runner,
|
|
32
32
|
localFile(identity.file) ?? "unknown",
|
|
33
33
|
identity.line ?? 0,
|
|
34
34
|
identity.column ?? 0,
|
|
35
35
|
identity.name,
|
|
36
|
-
]
|
|
36
|
+
];
|
|
37
|
+
// Preserve existing top-level, uniquely registered test IDs. Nested tests
|
|
38
|
+
// and repeated registrations need more than a shared source/name identity.
|
|
39
|
+
if (identity.parentTestId)
|
|
40
|
+
parts.push("parent", identity.parentTestId);
|
|
41
|
+
if (identity.registrationOrdinal)
|
|
42
|
+
parts.push("registration", identity.registrationOrdinal);
|
|
43
|
+
// Titles can themselves contain separator text. Domain-separate and encode
|
|
44
|
+
// the extended identity structurally so it cannot alias a literal title.
|
|
45
|
+
const key = identity.parentTestId || identity.registrationOrdinal
|
|
46
|
+
? JSON.stringify(["registration-v2", ...parts])
|
|
47
|
+
: parts.join("\0");
|
|
37
48
|
return `${identity.runner}:${createHash("sha256").update(key).digest("hex").slice(0, 24)}`;
|
|
38
49
|
}
|
|
39
50
|
export function runnerExecutionScope(identity) {
|
|
@@ -50,24 +61,35 @@ export function runnerExecutionScope(identity) {
|
|
|
50
61
|
testId,
|
|
51
62
|
testKey,
|
|
52
63
|
retry,
|
|
53
|
-
|
|
64
|
+
// Different processes/worker threads can execute the same registration.
|
|
65
|
+
// Their files must not collide even though the stable test ID is shared.
|
|
66
|
+
attemptId: `${testKey}-${retry}-${processInstanceToken()}`,
|
|
54
67
|
};
|
|
55
68
|
}
|
|
56
|
-
export function callerLocation(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
69
|
+
export function callerLocation(boundary = callerLocation) {
|
|
70
|
+
// Omit the registration wrapper by identity. A user file may have the same
|
|
71
|
+
// basename as a runtime adapter. Parse the location suffix, not individual
|
|
72
|
+
// path segments: spaces, parentheses and Unicode are valid filenames.
|
|
73
|
+
try {
|
|
74
|
+
const error = {};
|
|
75
|
+
Error.captureStackTrace(error, boundary);
|
|
76
|
+
if (typeof error.stack !== "string")
|
|
77
|
+
return {};
|
|
78
|
+
const entry = error.stack.split("\n")[1]?.trim();
|
|
79
|
+
const match = entry && /(?:^at (?:async )?| \()((?:file:\/\/|\/|[A-Za-z]:[\\/]|\\\\).*):(\d+):(\d+)\)?$/.exec(entry);
|
|
62
80
|
if (!match)
|
|
63
|
-
|
|
81
|
+
return {};
|
|
64
82
|
return {
|
|
65
83
|
file: match[1],
|
|
66
84
|
line: Number(match[2]),
|
|
67
85
|
column: Number(match[3]),
|
|
68
86
|
};
|
|
69
87
|
}
|
|
70
|
-
|
|
88
|
+
catch {
|
|
89
|
+
// Custom formatters must not prevent registration. Missing provenance
|
|
90
|
+
// is explicit, never replaced with an unrelated deeper caller.
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
71
93
|
}
|
|
72
94
|
export function readScopedServerEvidence(scope, evidencePath = serverEvidencePath(scope)) {
|
|
73
95
|
try {
|
|
@@ -721,9 +721,11 @@ function withCoverageCarrier(carrier, callback) {
|
|
|
721
721
|
function assertionPhaseState(scope) {
|
|
722
722
|
const key = attemptKey(scope);
|
|
723
723
|
const existing = state.assertionPhases.get(key);
|
|
724
|
-
if (existing)
|
|
724
|
+
if (existing) {
|
|
725
|
+
existing.phaseIds ??= new Set(existing.phases.filter(phase => phase.kind === "assertion").map(phase => phase.id));
|
|
725
726
|
return existing;
|
|
726
|
-
|
|
727
|
+
}
|
|
728
|
+
const created = { counter: 0, phases: [], phaseIds: new Set() };
|
|
727
729
|
state.assertionPhases.set(key, created);
|
|
728
730
|
return created;
|
|
729
731
|
}
|
|
@@ -753,9 +755,13 @@ function withNodeAssertionPhase(operation, source, callback) {
|
|
|
753
755
|
const scope = context.scope;
|
|
754
756
|
if (!scope)
|
|
755
757
|
return callback();
|
|
756
|
-
const existing = context.phaseId
|
|
758
|
+
const existing = context.phaseId && assertionPhaseState(scope).phaseIds.has(context.phaseId);
|
|
757
759
|
if (existing)
|
|
758
760
|
return callback();
|
|
761
|
+
// The adapter's stack is only a fallback. Avoid formatting it when a lexical
|
|
762
|
+
// probe already supplied the active phase's exact original-source identity.
|
|
763
|
+
if (typeof source === "function")
|
|
764
|
+
source = source();
|
|
759
765
|
const bridged = (_a8 = runtimeGlobal.__SUPERCOV_ASSERTION_PHASE_BRIDGE__) == null ? void 0 : _a8.call(runtimeGlobal, operation, source, callback);
|
|
760
766
|
if (bridged == null ? void 0 : bridged.handled)
|
|
761
767
|
return bridged.value;
|
|
@@ -768,6 +774,7 @@ function withNodeAssertionPhase(operation, source, callback) {
|
|
|
768
774
|
startedAtMs: Date.now()
|
|
769
775
|
});
|
|
770
776
|
attempt.phases.push(phase);
|
|
777
|
+
attempt.phaseIds.add(phase.id);
|
|
771
778
|
try {
|
|
772
779
|
const result = withCoverageCarrier({ version: 1, scope, phaseId: phase.id }, callback);
|
|
773
780
|
if (result && typeof result.then === "function")
|
|
@@ -785,6 +792,16 @@ function withNodeAssertionPhase(operation, source, callback) {
|
|
|
785
792
|
throw cleanInstrumentationStack(error);
|
|
786
793
|
}
|
|
787
794
|
}
|
|
795
|
+
// Resolve the target exactly once, before argument evaluation, just like a
|
|
796
|
+
// native call reference. The closure carries only source identity and receiver;
|
|
797
|
+
// it never captures an assertion phase or mutable current-statement state across
|
|
798
|
+
// await. Rejected arguments therefore create no phase, and awaited producer work
|
|
799
|
+
// is not retroactively attributed to the assertion invocation.
|
|
800
|
+
function bindNodeAssertion(operation, source, receiver, method) {
|
|
801
|
+
const target = method === void 0 ? receiver : receiver[method];
|
|
802
|
+
const thisArgument = method === void 0 ? void 0 : receiver;
|
|
803
|
+
return (...args) => withNodeAssertionPhase(operation, source, () => Reflect.apply(target, thisArgument, args));
|
|
804
|
+
}
|
|
788
805
|
function takeNodeAssertionPhases(scope) {
|
|
789
806
|
var _a8, _b;
|
|
790
807
|
const key = attemptKey(scope);
|
|
@@ -1293,6 +1310,7 @@ const directRuntimeApi = {
|
|
|
1293
1310
|
selectionEnd,
|
|
1294
1311
|
selectionRight,
|
|
1295
1312
|
takeNodeAssertionPhases,
|
|
1313
|
+
bindNodeAssertion,
|
|
1296
1314
|
testStatement,
|
|
1297
1315
|
tryBegin,
|
|
1298
1316
|
tryCatch,
|
|
@@ -1343,6 +1361,7 @@ export {
|
|
|
1343
1361
|
selectionEnd,
|
|
1344
1362
|
selectionRight,
|
|
1345
1363
|
takeNodeAssertionPhases,
|
|
1364
|
+
bindNodeAssertion,
|
|
1346
1365
|
testStatement,
|
|
1347
1366
|
tryBegin,
|
|
1348
1367
|
tryCatch,
|