vouchington-tooling 0.29.0 → 0.31.0
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 +69 -19
- package/bin/node-guard.mjs +9 -0
- package/bin/vouchington-mcp.mjs +20 -0
- package/bin/vouchington.mjs +18 -0
- package/dist/agent-blackboard/feedback-outbox-counts.d.mts +5 -0
- package/dist/agent-blackboard/feedback-outbox-counts.mjs +11 -0
- package/dist/agent-blackboard/feedback-outbox-directory.d.mts +2 -0
- package/dist/agent-blackboard/feedback-outbox-directory.mjs +16 -0
- package/dist/agent-blackboard/feedback-outbox.mjs +5 -7
- package/dist/agent-blackboard/index.d.mts +1 -0
- package/dist/agent-blackboard/index.mjs +1 -0
- package/dist/api-contract-discovery/header-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/protocol-body-status.d.mts +2 -0
- package/dist/api-contract-discovery/protocol-body-status.mjs +4 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.mjs +33 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +103 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.mjs +42 -0
- package/dist/api-contract-discovery/protocol-callback-registration.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-callback-registration.mjs +27 -0
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +17 -0
- package/dist/api-contract-discovery/protocol-callback-values.mjs +114 -0
- package/dist/api-contract-discovery/protocol-context-alias.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-context-alias.mjs +20 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.mjs +87 -0
- package/dist/api-contract-discovery/protocol-contract-keys.d.mts +1 -0
- package/dist/api-contract-discovery/protocol-contract-keys.mjs +8 -0
- package/dist/api-contract-discovery/protocol-contract-registry.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-contract-registry.mjs +133 -0
- package/dist/api-contract-discovery/protocol-executable-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-executable-path.mjs +64 -0
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-execution-path.mjs +16 -0
- package/dist/api-contract-discovery/protocol-http-association.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-association.mjs +97 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.mjs +19 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +34 -0
- package/dist/api-contract-discovery/protocol-http-context.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-http-context.mjs +108 -0
- package/dist/api-contract-discovery/protocol-http-emission.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-emission.mjs +76 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.mjs +26 -0
- package/dist/api-contract-discovery/protocol-http-registration.d.mts +12 -0
- package/dist/api-contract-discovery/protocol-http-registration.mjs +53 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.mjs +50 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.mjs +107 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +72 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.mjs +89 -0
- package/dist/api-contract-discovery/protocol-platform-writes.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-platform-writes.mjs +92 -0
- package/dist/api-contract-discovery/protocol-requested-keys.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-requested-keys.mjs +45 -0
- package/dist/api-contract-discovery/protocol-sse-callers.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-sse-callers.mjs +67 -0
- package/dist/api-contract-discovery/protocol-sse-emission.d.mts +11 -0
- package/dist/api-contract-discovery/protocol-sse-emission.mjs +61 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.mjs +24 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +70 -0
- package/dist/api-contract-discovery/protocol-sse-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-status.mjs +84 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.mjs +42 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +66 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +36 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.d.mts +14 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.mjs +79 -0
- package/dist/api-contract-discovery/protocol-status-dominance.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-status-dominance.mjs +35 -0
- package/dist/api-contract-discovery/protocol-write-receiver.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-write-receiver.mjs +79 -0
- package/dist/api-contract-discovery/query-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +30 -0
- package/dist/api-contract-discovery/registered-route-catalog.mjs +11 -7
- package/dist/api-contract-discovery/registered-route-factory-returns.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-factory-returns.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +5 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +79 -35
- package/dist/api-contract-discovery/registered-route-runtime-parameters.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-runtime-parameters.mjs +5 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.mjs +98 -0
- package/dist/api-contract-discovery/request-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/response-contract-implicit.mjs +28 -10
- package/dist/api-contract-discovery/response-contract-registry.mjs +14 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +22 -4
- package/dist/api-contract-discovery/response-contract-status.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-status.mjs +10 -8
- package/dist/api-contract-discovery/response-contract-types.d.mts +7 -0
- package/dist/cli/commands/agent-blackboard-flags.d.mts +4 -0
- package/dist/cli/commands/agent-blackboard-flags.mjs +34 -0
- package/dist/cli/commands/agent-blackboard.mjs +27 -35
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.mjs +12 -15
- package/dist/cli/parse-forwarded-commands.d.mts +0 -3
- package/dist/cli/parse-forwarded-commands.mjs +0 -2
- package/dist/cli/parse.d.mts +0 -3
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +2 -6
- package/dist/contract-schema/contract-schema-bounded-array.d.mts +1 -3
- package/dist/contract-schema/contract-schema-bounded-array.mjs +5 -0
- package/dist/contract-schema/contract-schema-object-tuple.mjs +7 -5
- package/dist/contract-schema/contract-schema-type-extractor.mjs +13 -0
- package/dist/contract-schema/contract-schema-validator.mjs +2 -2
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/mcp-server/dispatch.d.mts +4 -1
- package/dist/mcp-server/dispatch.mjs +6 -4
- package/dist/mcp-server/handlers-outbox.mjs +2 -7
- package/dist/mcp-server/harness.test-helpers.d.mts +1 -0
- package/dist/mcp-server/harness.test-helpers.mjs +1 -1
- package/dist/mcp-server/main.d.mts +5 -0
- package/dist/mcp-server/main.mjs +8 -0
- package/dist/mcp-server/run.d.mts +2 -0
- package/dist/mcp-server/run.mjs +4 -1
- package/dist/mcp-server/sdk.d.mts +11 -2
- package/dist/mcp-server/sdk.mjs +37 -5
- package/dist/mcp-server/server.mjs +3 -2
- package/dist/mcp-server/tools.mjs +3 -2
- package/dist/mcp-server/worktree.d.mts +17 -10
- package/dist/mcp-server/worktree.mjs +48 -31
- package/dist/openapi-document/build-openapi-catalog-sse.d.mts +4 -0
- package/dist/openapi-document/build-openapi-catalog-sse.mjs +27 -0
- package/dist/openapi-document/build-openapi-document.mjs +5 -2
- package/dist/openapi-document/build-openapi-response.d.mts +2 -2
- package/dist/openapi-document/build-openapi-response.mjs +33 -3
- package/dist/openapi-document/build-openapi-sse-events.d.mts +11 -0
- package/dist/openapi-document/build-openapi-sse-events.mjs +46 -0
- package/dist/openapi-document/contract-schema-intersection-merge.mjs +11 -11
- package/dist/openapi-document/contract-schema-to-openapi.mjs +3 -3
- package/dist/openapi-document/openapi-route-helpers.mjs +1 -3
- package/dist/openapi-document/openapi-types.d.mts +3 -0
- package/dist/openapi-document/operation-types.d.mts +7 -0
- package/docs/mcp-server.md +83 -25
- package/package.json +11 -9
- package/scripts/build.mjs +2 -0
- package/skills/blackboard/SKILL.md +66 -4
- package/skills/manifest.json +19 -19
- package/dist/agent-harness-config/apply.d.mts +0 -4
- package/dist/agent-harness-config/apply.mjs +0 -72
- package/dist/agent-harness-config/cli.d.mts +0 -10
- package/dist/agent-harness-config/cli.mjs +0 -100
- package/dist/agent-harness-config/index.d.mts +0 -4
- package/dist/agent-harness-config/index.mjs +0 -3
- package/dist/agent-harness-config/merge-json.d.mts +0 -5
- package/dist/agent-harness-config/merge-json.mjs +0 -62
- package/dist/agent-harness-config/merge-toml.d.mts +0 -6
- package/dist/agent-harness-config/merge-toml.mjs +0 -132
- package/dist/agent-harness-config/paths.d.mts +0 -3
- package/dist/agent-harness-config/paths.mjs +0 -117
- package/dist/agent-harness-config/policy.d.mts +0 -14
- package/dist/agent-harness-config/policy.mjs +0 -51
- package/dist/agent-harness-config/prerequisites.d.mts +0 -2
- package/dist/agent-harness-config/prerequisites.mjs +0 -67
- package/dist/agent-harness-config/toml-value.d.mts +0 -6
- package/dist/agent-harness-config/toml-value.mjs +0 -69
- package/dist/agent-harness-config/types.d.mts +0 -90
- package/dist/agent-harness-config/types.mjs +0 -2
- package/docs/agent-harness-config.md +0 -134
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { hasBindingWrite } from './registered-route-binding-writes.mjs';
|
|
3
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
4
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
5
|
+
import { executableProtocolPath } from './protocol-execution-path.mjs';
|
|
6
|
+
import { enclosingRouteBinding, } from './response-contract-route-analysis.mjs';
|
|
7
|
+
import { expressionReceiver } from './protocol-write-receiver.mjs';
|
|
8
|
+
export function routeKey(binding) {
|
|
9
|
+
return `${binding.method}:${binding.routeTemplate}`;
|
|
10
|
+
}
|
|
11
|
+
function implementationDeclaration(declaration, checker) {
|
|
12
|
+
if (!declaration)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration))
|
|
15
|
+
return declaration;
|
|
16
|
+
if (ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration)) {
|
|
17
|
+
if (declaration.body)
|
|
18
|
+
return declaration;
|
|
19
|
+
const symbol = declaration.name && checker.getSymbolAtLocation(declaration.name);
|
|
20
|
+
return symbol?.declarations?.find((candidate) => (ts.isFunctionDeclaration(candidate) || ts.isMethodDeclaration(candidate)) &&
|
|
21
|
+
!!candidate.body);
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function helperBindings(node, calls, checker, bindings) {
|
|
26
|
+
const fn = enclosingFunction(node);
|
|
27
|
+
if (!fn || fn.asteriskToken)
|
|
28
|
+
return [];
|
|
29
|
+
return calls.flatMap((call) => {
|
|
30
|
+
if (implementationDeclaration(checker.getResolvedSignature(call)?.declaration, checker) !== fn ||
|
|
31
|
+
!executableProtocolPath(call, checker))
|
|
32
|
+
return [];
|
|
33
|
+
const binding = enclosingRouteBinding(call, checker, bindings, false);
|
|
34
|
+
return binding ? [binding] : [];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function actualReceivers(receiver, binding, calls, checker, bindings, active = new Set()) {
|
|
38
|
+
const declaration = receiver.root.valueDeclaration;
|
|
39
|
+
if (receiver.mutableAlias ||
|
|
40
|
+
(declaration &&
|
|
41
|
+
(ts.isBindingElement(declaration) ||
|
|
42
|
+
(ts.isVariableDeclaration(declaration) &&
|
|
43
|
+
!(declaration.parent.flags & ts.NodeFlags.Const)) ||
|
|
44
|
+
(ts.isParameter(declaration) && hasBindingWrite(declaration, checker)))))
|
|
45
|
+
return [undefined];
|
|
46
|
+
const fn = declaration && enclosingFunction(declaration);
|
|
47
|
+
if (!declaration || !ts.isParameter(declaration) || !fn)
|
|
48
|
+
return [receiver];
|
|
49
|
+
if (active.has(receiver.root))
|
|
50
|
+
return [undefined];
|
|
51
|
+
const index = runtimeParameters(fn).indexOf(declaration);
|
|
52
|
+
const actuals = calls.flatMap((call) => {
|
|
53
|
+
if (implementationDeclaration(checker.getResolvedSignature(call)?.declaration, checker) !== fn ||
|
|
54
|
+
!executableProtocolPath(call, checker))
|
|
55
|
+
return [];
|
|
56
|
+
const callBinding = enclosingRouteBinding(call, checker, bindings, false);
|
|
57
|
+
if (!callBinding || routeKey(callBinding) !== routeKey(binding))
|
|
58
|
+
return [];
|
|
59
|
+
const argument = call.arguments[index] ?? declaration.initializer;
|
|
60
|
+
const actual = argument && expressionReceiver(argument, checker);
|
|
61
|
+
if (!actual || receiver.path.length)
|
|
62
|
+
return [undefined];
|
|
63
|
+
return actualReceivers(actual, binding, calls, checker, bindings, new Set(active).add(receiver.root));
|
|
64
|
+
});
|
|
65
|
+
return actuals.length ? actuals : [receiver];
|
|
66
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import { type WriteReceiver } from './protocol-write-receiver.mts';
|
|
3
|
+
export type SseWriteMutation = {
|
|
4
|
+
node: ts.Node;
|
|
5
|
+
receiver: ts.Expression;
|
|
6
|
+
};
|
|
7
|
+
export declare function sseWriteMutation(node: ts.Node): SseWriteMutation | undefined;
|
|
8
|
+
export declare function mutationAffectsSelectedStream(mutations: readonly SseWriteMutation[], frameReceivers: readonly WriteReceiver[], checker: ts.TypeChecker, isSameRoute: (node: ts.Node) => boolean, resolveActual: (expression: ts.Expression) => readonly (WriteReceiver | undefined)[] | undefined, resolveFrame: (receiver: WriteReceiver) => readonly (WriteReceiver | undefined)[]): boolean;
|
|
9
|
+
export declare function isSourceLevelMutation(node: ts.Node): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { executableProtocolPath } from './protocol-execution-path.mjs';
|
|
3
|
+
import { sameWriteReceiver } from './protocol-write-receiver.mjs';
|
|
4
|
+
import { writeAccess } from './protocol-sse-write-resolution.mjs';
|
|
5
|
+
export function sseWriteMutation(node) {
|
|
6
|
+
if (ts.isBinaryExpression(node) && isAssignment(node.operatorToken.kind)) {
|
|
7
|
+
const access = writeAccess(node.left);
|
|
8
|
+
if (access && (access.method === 'write' || access.method === 'end'))
|
|
9
|
+
return { node, receiver: access.receiver };
|
|
10
|
+
}
|
|
11
|
+
if (ts.isDeleteExpression(node)) {
|
|
12
|
+
const access = writeAccess(node.expression);
|
|
13
|
+
if (access && (access.method === 'write' || access.method === 'end'))
|
|
14
|
+
return { node, receiver: access.receiver };
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function isAssignment(kind) {
|
|
19
|
+
return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;
|
|
20
|
+
}
|
|
21
|
+
export function mutationAffectsSelectedStream(mutations, frameReceivers, checker, isSameRoute, resolveActual, resolveFrame) {
|
|
22
|
+
const framed = frameReceivers.flatMap(resolveFrame);
|
|
23
|
+
return mutations.some(({ node, receiver }) => {
|
|
24
|
+
if ((!isSourceLevelMutation(node) && !executableProtocolPath(node, checker)) ||
|
|
25
|
+
!isSameRoute(node))
|
|
26
|
+
return false;
|
|
27
|
+
const actual = resolveActual(receiver);
|
|
28
|
+
if (!actual)
|
|
29
|
+
return true;
|
|
30
|
+
return actual.some((value) => value === undefined ||
|
|
31
|
+
framed.some((frame) => frame === undefined || sameWriteReceiver(frame, value)));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function isSourceLevelMutation(node) {
|
|
35
|
+
return ts.isExpressionStatement(node.parent) && ts.isSourceFile(node.parent.parent);
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export type WriteAccess = {
|
|
3
|
+
receiver: ts.Expression;
|
|
4
|
+
method: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
export type WriteInvocation = {
|
|
7
|
+
receiver: ts.Expression | undefined;
|
|
8
|
+
method: string | undefined;
|
|
9
|
+
rawBytes: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function writeAccess(expression: ts.Expression): WriteAccess | undefined;
|
|
12
|
+
export declare function reflectApplyInvocation(call: ts.CallExpression, checker: ts.TypeChecker): WriteInvocation | undefined;
|
|
13
|
+
export declare function indirectPayload(method: 'call' | 'apply', call: ts.CallExpression, checker: ts.TypeChecker): readonly ts.Expression[] | undefined;
|
|
14
|
+
export declare function hasRawBytes(method: string | undefined, payload: readonly ts.Expression[] | undefined, checker: ts.TypeChecker, indirect: boolean): boolean;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
3
|
+
export function writeAccess(expression) {
|
|
4
|
+
expression = unwrapExpression(expression);
|
|
5
|
+
if (ts.isPropertyAccessExpression(expression))
|
|
6
|
+
return { receiver: expression.expression, method: expression.name.text };
|
|
7
|
+
if (!ts.isElementAccessExpression(expression))
|
|
8
|
+
return undefined;
|
|
9
|
+
const method = unwrapExpression(expression.argumentExpression);
|
|
10
|
+
return {
|
|
11
|
+
receiver: expression.expression,
|
|
12
|
+
method: ts.isStringLiteralLike(method) ? method.text : undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function reflectApplyInvocation(call, checker) {
|
|
16
|
+
const expression = unwrapExpression(call.expression);
|
|
17
|
+
if (!ts.isPropertyAccessExpression(expression) ||
|
|
18
|
+
expression.name.text !== 'apply' ||
|
|
19
|
+
!ts.isIdentifier(expression.expression) ||
|
|
20
|
+
expression.expression.text !== 'Reflect')
|
|
21
|
+
return undefined;
|
|
22
|
+
const defaultApply = checker
|
|
23
|
+
.getSymbolAtLocation(expression.name)
|
|
24
|
+
?.declarations?.some((declaration) => declaration.getSourceFile().fileName.endsWith('/lib.es2015.reflect.d.ts'));
|
|
25
|
+
if (!defaultApply)
|
|
26
|
+
return undefined;
|
|
27
|
+
const target = call.arguments[0] && writeAccess(call.arguments[0]);
|
|
28
|
+
const receiver = call.arguments[1];
|
|
29
|
+
const payload = applyPayload(call.arguments[2], checker);
|
|
30
|
+
if (target?.method === 'pipe') {
|
|
31
|
+
const destination = payload?.[0];
|
|
32
|
+
return {
|
|
33
|
+
receiver: destination,
|
|
34
|
+
method: 'write',
|
|
35
|
+
rawBytes: payload === undefined || destination !== undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const method = target?.method === 'write' || target?.method === 'end' ? target.method : undefined;
|
|
39
|
+
return {
|
|
40
|
+
receiver,
|
|
41
|
+
method,
|
|
42
|
+
rawBytes: payload === undefined || hasRawBytes(method, payload, checker, true),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function indirectPayload(method, call, checker) {
|
|
46
|
+
if (method === 'call')
|
|
47
|
+
return call.arguments.slice(1);
|
|
48
|
+
return applyPayload(call.arguments[1], checker);
|
|
49
|
+
}
|
|
50
|
+
function applyPayload(argument, checker) {
|
|
51
|
+
if (!argument)
|
|
52
|
+
return [];
|
|
53
|
+
const value = unwrapExpression(argument);
|
|
54
|
+
if (isNullish(argument, checker))
|
|
55
|
+
return [];
|
|
56
|
+
if (!ts.isArrayLiteralExpression(value) || value.elements.some(ts.isSpreadElement))
|
|
57
|
+
return undefined;
|
|
58
|
+
return value.elements.filter(ts.isExpression);
|
|
59
|
+
}
|
|
60
|
+
function isNullish(expression, checker) {
|
|
61
|
+
const value = unwrapExpression(expression);
|
|
62
|
+
if (value.kind === ts.SyntaxKind.NullKeyword)
|
|
63
|
+
return true;
|
|
64
|
+
if (ts.isIdentifier(value) && value.text === 'undefined')
|
|
65
|
+
return true;
|
|
66
|
+
const flags = checker.getTypeAtLocation(expression).flags;
|
|
67
|
+
return !!(flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null));
|
|
68
|
+
}
|
|
69
|
+
export function hasRawBytes(method, payload, checker, indirect) {
|
|
70
|
+
if (payload === undefined || method === undefined)
|
|
71
|
+
return true;
|
|
72
|
+
if (method === 'write')
|
|
73
|
+
return !indirect || payload.length > 0;
|
|
74
|
+
if (method !== 'end' || !payload[0])
|
|
75
|
+
return false;
|
|
76
|
+
const type = checker.getTypeAtLocation(payload[0]);
|
|
77
|
+
return (!(type.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)) &&
|
|
78
|
+
type.getCallSignatures().length === 0);
|
|
79
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** Requires the last potentially preceding proven status setter to belong to this response. */
|
|
3
|
+
export declare function statusDominatesEmission(emission: ts.CallExpression | ts.NewExpression, statuses: ReadonlySet<ts.CallExpression>, allStatuses?: ReadonlySet<ts.CallExpression>): boolean;
|
|
4
|
+
/** Finds the last setter through unconditional blocks, excluding conditional compound statements. */
|
|
5
|
+
export declare function unconditionalStatusSetter(statement: ts.Statement, statuses: ReadonlySet<ts.CallExpression>): ts.CallExpression | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
/** Requires the last potentially preceding proven status setter to belong to this response. */
|
|
3
|
+
export function statusDominatesEmission(emission, statuses, allStatuses = statuses) {
|
|
4
|
+
let current = emission;
|
|
5
|
+
while (current.parent && !ts.isFunctionLike(current.parent)) {
|
|
6
|
+
const parent = current.parent;
|
|
7
|
+
if (ts.isBlock(parent)) {
|
|
8
|
+
const index = parent.statements.indexOf(current);
|
|
9
|
+
for (const statement of parent.statements.slice(0, index).toReversed())
|
|
10
|
+
if (containsStatus(statement, allStatuses)) {
|
|
11
|
+
const setter = unconditionalStatusSetter(statement, allStatuses);
|
|
12
|
+
return !!setter && statuses.has(setter);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
current = parent;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
/** Finds the last setter through unconditional blocks, excluding conditional compound statements. */
|
|
20
|
+
export function unconditionalStatusSetter(statement, statuses) {
|
|
21
|
+
if (ts.isBlock(statement)) {
|
|
22
|
+
for (const child of statement.statements.toReversed())
|
|
23
|
+
if (containsStatus(child, statuses))
|
|
24
|
+
return unconditionalStatusSetter(child, statuses);
|
|
25
|
+
}
|
|
26
|
+
else if (ts.isExpressionStatement(statement) &&
|
|
27
|
+
ts.isCallExpression(statement.expression) &&
|
|
28
|
+
statuses.has(statement.expression))
|
|
29
|
+
return statement.expression;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function containsStatus(node, statuses) {
|
|
33
|
+
return ((ts.isCallExpression(node) && statuses.has(node)) ||
|
|
34
|
+
(ts.forEachChild(node, (child) => containsStatus(child, statuses)) ?? false));
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export type WriteReceiver = {
|
|
3
|
+
root: ts.Symbol;
|
|
4
|
+
path: readonly string[];
|
|
5
|
+
mutableAlias?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function writeReceiver(call: ts.CallExpression, checker: ts.TypeChecker): WriteReceiver | undefined;
|
|
8
|
+
export declare function sameWriteReceiver(left: WriteReceiver, right: WriteReceiver | undefined): boolean;
|
|
9
|
+
export declare function expressionReceiver(expression: ts.Expression, checker: ts.TypeChecker, active?: Set<ts.Symbol>): WriteReceiver | undefined;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
3
|
+
export function writeReceiver(call, checker) {
|
|
4
|
+
const target = unwrapExpression(call.expression);
|
|
5
|
+
if (!ts.isPropertyAccessExpression(target) || target.name.text !== 'write')
|
|
6
|
+
return undefined;
|
|
7
|
+
return expressionReceiver(target.expression, checker);
|
|
8
|
+
}
|
|
9
|
+
export function sameWriteReceiver(left, right) {
|
|
10
|
+
return (right !== undefined &&
|
|
11
|
+
left.root === right.root &&
|
|
12
|
+
left.path.length === right.path.length &&
|
|
13
|
+
left.path.every((part, index) => part === right.path[index]));
|
|
14
|
+
}
|
|
15
|
+
export function expressionReceiver(expression, checker, active = new Set()) {
|
|
16
|
+
const value = unwrapExpression(expression);
|
|
17
|
+
if (ts.isIdentifier(value)) {
|
|
18
|
+
const symbol = checker.getSymbolAtLocation(value);
|
|
19
|
+
if (!symbol)
|
|
20
|
+
return undefined;
|
|
21
|
+
return symbolReceiver(symbol, checker, active);
|
|
22
|
+
}
|
|
23
|
+
if (!ts.isPropertyAccessExpression(value))
|
|
24
|
+
return undefined;
|
|
25
|
+
const propertySymbol = checker.getSymbolAtLocation(value.name);
|
|
26
|
+
if (propertySymbol && active.has(propertySymbol))
|
|
27
|
+
return undefined;
|
|
28
|
+
const property = literalProperty(value, checker) ?? propertySymbol?.valueDeclaration;
|
|
29
|
+
const propertyActive = new Set(active);
|
|
30
|
+
if (propertySymbol)
|
|
31
|
+
propertyActive.add(propertySymbol);
|
|
32
|
+
// Object literal properties remain writable even when their variable is const.
|
|
33
|
+
// Track the original receiver for raw-write rejection; never trust a marked wrapper.
|
|
34
|
+
if (property && ts.isPropertyAssignment(property)) {
|
|
35
|
+
const receiver = expressionReceiver(property.initializer, checker, propertyActive);
|
|
36
|
+
return receiver && { ...receiver, mutableAlias: true };
|
|
37
|
+
}
|
|
38
|
+
if (property && ts.isShorthandPropertyAssignment(property)) {
|
|
39
|
+
const symbol = checker.getShorthandAssignmentValueSymbol(property);
|
|
40
|
+
const receiver = symbol && symbolReceiver(symbol, checker, propertyActive);
|
|
41
|
+
return receiver && { ...receiver, mutableAlias: true };
|
|
42
|
+
}
|
|
43
|
+
const parent = expressionReceiver(value.expression, checker, active);
|
|
44
|
+
return parent ? { ...parent, path: [...parent.path, value.name.text] } : undefined;
|
|
45
|
+
}
|
|
46
|
+
function symbolReceiver(symbol, checker, active) {
|
|
47
|
+
const root = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
48
|
+
if (active.has(root))
|
|
49
|
+
return undefined;
|
|
50
|
+
const declaration = root.valueDeclaration;
|
|
51
|
+
if (declaration &&
|
|
52
|
+
ts.isVariableDeclaration(declaration) &&
|
|
53
|
+
declaration.initializer &&
|
|
54
|
+
ts.isVariableDeclarationList(declaration.parent)) {
|
|
55
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
56
|
+
if (ts.isIdentifier(initializer) || ts.isPropertyAccessExpression(initializer)) {
|
|
57
|
+
const receiver = expressionReceiver(initializer, checker, new Set(active).add(root));
|
|
58
|
+
return (receiver && {
|
|
59
|
+
...receiver,
|
|
60
|
+
...(!(declaration.parent.flags & ts.NodeFlags.Const) ? { mutableAlias: true } : {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { root, path: [] };
|
|
65
|
+
}
|
|
66
|
+
function literalProperty(value, checker) {
|
|
67
|
+
const base = unwrapExpression(value.expression);
|
|
68
|
+
if (!ts.isIdentifier(base))
|
|
69
|
+
return undefined;
|
|
70
|
+
const declaration = checker.getSymbolAtLocation(base)?.valueDeclaration;
|
|
71
|
+
if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer)
|
|
72
|
+
return undefined;
|
|
73
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
74
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
75
|
+
return undefined;
|
|
76
|
+
return initializer.properties.find((member) => (ts.isPropertyAssignment(member) || ts.isShorthandPropertyAssignment(member)) &&
|
|
77
|
+
ts.isIdentifier(member.name) &&
|
|
78
|
+
member.name.text === value.name.text);
|
|
79
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
|
|
2
3
|
import { extractQueryParameterDescriptor } from './query-contract-extraction.mjs';
|
|
3
4
|
import { contractError } from './response-contract-registration.mjs';
|
|
4
5
|
import { collectHandlerBindings, enclosingRouteBinding, visit, } from './response-contract-route-analysis.mjs';
|
|
5
6
|
export function discoverApiQueryContracts(program, sourceFiles, knownResponseRoutes) {
|
|
7
|
+
registerPlatformCompilerLibraries(program);
|
|
6
8
|
const checker = program.getTypeChecker();
|
|
7
9
|
const handlerBindings = collectHandlerBindings(sourceFiles, checker);
|
|
8
10
|
const contracts = new Map();
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** Helper and factory parameter bindings must remain unchanged in their declaring source. */
|
|
3
|
+
export declare function hasBindingWrite(declaration: ts.FunctionDeclaration | ts.ParameterDeclaration, checker: ts.TypeChecker): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
/** Helper and factory parameter bindings must remain unchanged in their declaring source. */
|
|
3
|
+
export function hasBindingWrite(declaration, checker) {
|
|
4
|
+
if (!declaration.name)
|
|
5
|
+
return false;
|
|
6
|
+
if (!ts.isIdentifier(declaration.name))
|
|
7
|
+
return true;
|
|
8
|
+
const binding = checker.getSymbolAtLocation(declaration.name);
|
|
9
|
+
let written = false;
|
|
10
|
+
const referencesBinding = (node) => {
|
|
11
|
+
if (ts.isIdentifier(node) && checker.getSymbolAtLocation(node) === binding)
|
|
12
|
+
written = true;
|
|
13
|
+
node.forEachChild(referencesBinding);
|
|
14
|
+
};
|
|
15
|
+
const inspect = (node) => {
|
|
16
|
+
if (ts.isBinaryExpression(node) &&
|
|
17
|
+
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
18
|
+
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
|
|
19
|
+
referencesBinding(node.left);
|
|
20
|
+
if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
21
|
+
(node.operator === ts.SyntaxKind.PlusPlusToken ||
|
|
22
|
+
node.operator === ts.SyntaxKind.MinusMinusToken))
|
|
23
|
+
referencesBinding(node.operand);
|
|
24
|
+
if (ts.isForOfStatement(node) || ts.isForInStatement(node))
|
|
25
|
+
referencesBinding(node.initializer);
|
|
26
|
+
node.forEachChild(inspect);
|
|
27
|
+
};
|
|
28
|
+
inspect(declaration.getSourceFile());
|
|
29
|
+
return written;
|
|
30
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
2
|
import { handlerNodes } from './registered-route-handler-analysis.mjs';
|
|
3
|
+
import { isTerminal405Handler } from './registered-route-terminal-error.mjs';
|
|
3
4
|
import { propertyName, routeTemplateFromExpression, visit, } from './response-contract-route-analysis.mjs';
|
|
4
5
|
export const routeShape = (routeTemplate) => routeTemplate.replace(/:[^/]+/g, ':');
|
|
5
6
|
export function discoverRegisteredRoutes(program, sourceFiles) {
|
|
@@ -39,7 +40,16 @@ function classifyHandler(call, checker, method, routeTemplate) {
|
|
|
39
40
|
if (nodes.length === 0)
|
|
40
41
|
throw new Error(`Cannot inspect registered route handler ${method}:${routeTemplate}`);
|
|
41
42
|
let sse = false;
|
|
42
|
-
|
|
43
|
+
const proofGroups = call.arguments
|
|
44
|
+
.filter((argument) => !ts.isStringLiteral(argument))
|
|
45
|
+
.map((argument) => {
|
|
46
|
+
const proofs = [];
|
|
47
|
+
const nodes = handlerNodes(argument, checker, new Map(), new Set(), true, proofs);
|
|
48
|
+
return nodes.some(ts.isFunctionLike) ? proofs : [];
|
|
49
|
+
});
|
|
50
|
+
const error405 = proofGroups.length > 0 &&
|
|
51
|
+
proofGroups.every((handlers) => handlers.length > 0 &&
|
|
52
|
+
handlers.every((proof) => isTerminal405Handler(proof.node, checker, proof.bindings)));
|
|
43
53
|
let fixedStatus;
|
|
44
54
|
nodes.forEach((node) => {
|
|
45
55
|
visit(node, (child) => {
|
|
@@ -64,12 +74,6 @@ function classifyHandler(call, checker, method, routeTemplate) {
|
|
|
64
74
|
ts.isStringLiteral(child.arguments[0]) &&
|
|
65
75
|
child.arguments[0].text.toLowerCase() === 'text/event-stream')
|
|
66
76
|
sse = true;
|
|
67
|
-
if (ts.isPropertyAccessExpression(child.expression) &&
|
|
68
|
-
child.expression.name.text === 'throw' &&
|
|
69
|
-
child.arguments[0] &&
|
|
70
|
-
ts.isNumericLiteral(child.arguments[0]) &&
|
|
71
|
-
child.arguments[0].text === '405')
|
|
72
|
-
error405 = true;
|
|
73
77
|
});
|
|
74
78
|
});
|
|
75
79
|
if (fixedStatus !== undefined) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
export function returnedExpressions(declaration) {
|
|
3
|
+
const body = declaration.body;
|
|
4
|
+
if (!ts.isBlock(body))
|
|
5
|
+
return [body];
|
|
6
|
+
const returned = [];
|
|
7
|
+
const collect = (node) => {
|
|
8
|
+
if (node !== declaration && ts.isFunctionLike(node))
|
|
9
|
+
return;
|
|
10
|
+
if (ts.isReturnStatement(node)) {
|
|
11
|
+
returned.push(node.expression);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
node.forEachChild(collect);
|
|
15
|
+
};
|
|
16
|
+
body.forEachChild(collect);
|
|
17
|
+
return returned;
|
|
18
|
+
}
|
|
19
|
+
export function returnsOnEveryPath(node) {
|
|
20
|
+
if (ts.isBlock(node))
|
|
21
|
+
return node.statements.some(returnsOnEveryPath);
|
|
22
|
+
if (ts.isReturnStatement(node))
|
|
23
|
+
return true;
|
|
24
|
+
if (ts.isIfStatement(node))
|
|
25
|
+
return (!!node.elseStatement &&
|
|
26
|
+
returnsOnEveryPath(node.thenStatement) &&
|
|
27
|
+
returnsOnEveryPath(node.elseStatement));
|
|
28
|
+
return !ts.isStatement(node);
|
|
29
|
+
}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
-
export
|
|
2
|
+
export type HandlerProof = {
|
|
3
|
+
node: ts.FunctionLikeDeclaration;
|
|
4
|
+
bindings: Map<ts.Symbol, ts.Expression>;
|
|
5
|
+
};
|
|
6
|
+
export declare function handlerNodes(argument: ts.Expression, checker: ts.TypeChecker, parameterBindings?: Map<ts.Symbol, ts.Expression>, active?: Set<ts.Node>, staticProof?: boolean, proofs?: HandlerProof[]): ts.Node[];
|
|
@@ -1,74 +1,118 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { returnedExpressions, returnsOnEveryPath } from './registered-route-factory-returns.mjs';
|
|
3
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
4
|
+
import { hasBindingWrite } from './registered-route-binding-writes.mjs';
|
|
5
|
+
export function handlerNodes(argument, checker, parameterBindings = new Map(), active = new Set(), staticProof = false, proofs) {
|
|
6
|
+
const unwrapped = unwrapHandlerExpression(argument);
|
|
7
|
+
if (unwrapped !== argument)
|
|
8
|
+
return handlerNodes(unwrapped, checker, parameterBindings, active, staticProof, proofs);
|
|
9
|
+
let bound = argument;
|
|
10
|
+
const boundIdentifiers = new Set();
|
|
11
|
+
while (ts.isIdentifier(bound)) {
|
|
12
|
+
const symbol = checker.getSymbolAtLocation(bound);
|
|
13
|
+
const nextArgument = symbol && parameterBindings.get(symbol);
|
|
14
|
+
if (!nextArgument)
|
|
15
|
+
break;
|
|
16
|
+
if (boundIdentifiers.has(bound))
|
|
17
|
+
return [];
|
|
18
|
+
boundIdentifiers.add(bound);
|
|
19
|
+
bound = nextArgument;
|
|
8
20
|
}
|
|
9
|
-
if (
|
|
21
|
+
if (bound !== argument)
|
|
22
|
+
return handlerNodes(bound, checker, parameterBindings, active, staticProof, proofs);
|
|
23
|
+
if (active.has(argument))
|
|
24
|
+
return [];
|
|
25
|
+
const next = new Set(active).add(argument);
|
|
26
|
+
if (ts.isArrowFunction(argument) || ts.isFunctionExpression(argument)) {
|
|
27
|
+
proofs?.push({ node: argument, bindings: parameterBindings });
|
|
10
28
|
return [argument];
|
|
29
|
+
}
|
|
11
30
|
if (ts.isCallExpression(argument)) {
|
|
12
|
-
|
|
31
|
+
const resolvedProofs = [];
|
|
32
|
+
const groups = callableImplementations(argument.expression, checker, staticProof).map((implementation) => {
|
|
33
|
+
if (staticProof &&
|
|
34
|
+
(implementation.asteriskToken ||
|
|
35
|
+
implementation.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
36
|
+
runtimeParameters(implementation).some((parameter) => hasBindingWrite(parameter, checker))))
|
|
37
|
+
return [];
|
|
13
38
|
const bindings = new Map(parameterBindings);
|
|
14
|
-
implementation.
|
|
39
|
+
runtimeParameters(implementation).forEach((parameter, index) => {
|
|
15
40
|
const callArgument = argument.arguments[index];
|
|
16
41
|
const symbol = checker.getSymbolAtLocation(parameter.name);
|
|
17
42
|
if (callArgument && symbol)
|
|
18
43
|
bindings.set(symbol, callArgument);
|
|
19
44
|
});
|
|
20
|
-
|
|
45
|
+
const returned = returnedExpressions(implementation);
|
|
46
|
+
if (staticProof &&
|
|
47
|
+
(!returnsOnEveryPath(implementation.body) || returned.some((value) => !value)))
|
|
48
|
+
return [];
|
|
49
|
+
const results = returned.map((value) => value ? handlerNodes(value, checker, bindings, next, staticProof, resolvedProofs) : []);
|
|
50
|
+
return staticProof && results.some((result) => !result.some(ts.isFunctionLike))
|
|
51
|
+
? []
|
|
52
|
+
: results.flat();
|
|
21
53
|
});
|
|
54
|
+
if (staticProof && groups.some((group) => group.length === 0))
|
|
55
|
+
return [];
|
|
56
|
+
proofs?.push(...resolvedProofs);
|
|
57
|
+
return groups.flat();
|
|
22
58
|
}
|
|
23
|
-
return declarationImplementations(argument, checker);
|
|
59
|
+
return declarationImplementations(argument, checker, next, staticProof, parameterBindings, proofs);
|
|
24
60
|
}
|
|
25
|
-
function callableImplementations(node, checker) {
|
|
61
|
+
function callableImplementations(node, checker, staticProof = false) {
|
|
26
62
|
const symbol = checker.getSymbolAtLocation(node);
|
|
27
63
|
if (!symbol)
|
|
28
64
|
return [];
|
|
29
65
|
const resolved = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
30
66
|
const implementations = [];
|
|
31
67
|
for (const declaration of resolved.declarations ?? []) {
|
|
68
|
+
if (staticProof && !hasStableBinding(declaration, checker))
|
|
69
|
+
continue;
|
|
32
70
|
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
33
|
-
const initializer = declaration.initializer;
|
|
71
|
+
const initializer = unwrapHandlerExpression(declaration.initializer);
|
|
34
72
|
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer))
|
|
35
73
|
implementations.push(initializer);
|
|
36
74
|
continue;
|
|
37
75
|
}
|
|
38
|
-
if ((ts.isFunctionDeclaration(declaration) ||
|
|
76
|
+
if ((ts.isFunctionDeclaration(declaration) ||
|
|
77
|
+
(!staticProof && ts.isMethodDeclaration(declaration))) &&
|
|
39
78
|
declaration.body)
|
|
40
79
|
implementations.push(declaration);
|
|
41
80
|
}
|
|
42
81
|
return implementations;
|
|
43
82
|
}
|
|
44
|
-
function
|
|
45
|
-
const body = declaration.body;
|
|
46
|
-
if (!ts.isBlock(body))
|
|
47
|
-
return [body];
|
|
48
|
-
const returned = [];
|
|
49
|
-
const collect = (node) => {
|
|
50
|
-
if (node !== declaration && ts.isFunctionLike(node))
|
|
51
|
-
return;
|
|
52
|
-
if (ts.isReturnStatement(node)) {
|
|
53
|
-
if (node.expression)
|
|
54
|
-
returned.push(node.expression);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
node.forEachChild(collect);
|
|
58
|
-
};
|
|
59
|
-
body.forEachChild(collect);
|
|
60
|
-
return returned;
|
|
61
|
-
}
|
|
62
|
-
function declarationImplementations(node, checker) {
|
|
83
|
+
function declarationImplementations(node, checker, active, staticProof, parameterBindings, proofs) {
|
|
63
84
|
const symbol = checker.getSymbolAtLocation(node);
|
|
64
85
|
if (!symbol)
|
|
65
86
|
return [];
|
|
66
87
|
const resolved = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
67
88
|
return (resolved.declarations ?? []).flatMap((declaration) => {
|
|
68
|
-
if (
|
|
89
|
+
if (staticProof && !hasStableBinding(declaration, checker))
|
|
90
|
+
return [];
|
|
91
|
+
if (ts.isFunctionDeclaration(declaration) ||
|
|
92
|
+
(!staticProof && ts.isMethodDeclaration(declaration))) {
|
|
93
|
+
if (declaration.body)
|
|
94
|
+
proofs?.push({ node: declaration, bindings: parameterBindings });
|
|
69
95
|
return declaration.body ? [declaration] : [];
|
|
96
|
+
}
|
|
70
97
|
if (ts.isVariableDeclaration(declaration) && declaration.initializer)
|
|
71
|
-
return [
|
|
98
|
+
return [
|
|
99
|
+
declaration,
|
|
100
|
+
...handlerNodes(declaration.initializer, checker, parameterBindings, active, staticProof, proofs),
|
|
101
|
+
];
|
|
72
102
|
return [];
|
|
73
103
|
});
|
|
74
104
|
}
|
|
105
|
+
function hasStableBinding(declaration, checker) {
|
|
106
|
+
if (ts.isVariableDeclaration(declaration))
|
|
107
|
+
return (ts.isVariableDeclarationList(declaration.parent) &&
|
|
108
|
+
!!(declaration.parent.flags & ts.NodeFlags.Const));
|
|
109
|
+
return !ts.isFunctionDeclaration(declaration) || !hasBindingWrite(declaration, checker);
|
|
110
|
+
}
|
|
111
|
+
function unwrapHandlerExpression(node) {
|
|
112
|
+
while (ts.isParenthesizedExpression(node) ||
|
|
113
|
+
ts.isAsExpression(node) ||
|
|
114
|
+
ts.isTypeAssertionExpression(node) ||
|
|
115
|
+
ts.isSatisfiesExpression(node))
|
|
116
|
+
node = node.expression;
|
|
117
|
+
return node;
|
|
118
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
/** TypeScript's explicit this parameter is erased and consumes no runtime argument. */
|
|
3
|
+
export function runtimeParameters(node) {
|
|
4
|
+
return node.parameters.filter((parameter) => !ts.isIdentifier(parameter.name) || parameter.name.text !== 'this');
|
|
5
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** Proves a terminal 405 on the handler's own context, rather than finding a nested throw. */
|
|
3
|
+
export declare function isTerminal405Handler(node: ts.Node, checker: ts.TypeChecker, bindings?: Map<ts.Symbol, ts.Expression>): boolean;
|