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,34 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { createProtocolCallbackValueResolver } from './protocol-callback-values.mjs';
|
|
3
|
+
import { callbackArgumentBindings } from './protocol-callback-argument-bindings.mjs';
|
|
4
|
+
import { registeredHandler } from './protocol-callback-registration.mjs';
|
|
5
|
+
import { executableProtocolPath } from './protocol-execution-path.mjs';
|
|
6
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
7
|
+
/** Use the same actual invocation sites for context admission and caller emission coverage. */
|
|
8
|
+
export function httpContextInvocations(fn, checker) {
|
|
9
|
+
const resolver = createProtocolCallbackValueResolver(checker);
|
|
10
|
+
const calls = [];
|
|
11
|
+
function visit(node) {
|
|
12
|
+
if (ts.isCallExpression(node) && executableProtocolPath(node, checker))
|
|
13
|
+
calls.push(node);
|
|
14
|
+
ts.forEachChild(node, visit);
|
|
15
|
+
}
|
|
16
|
+
visit(fn.getSourceFile());
|
|
17
|
+
const registered = calls.some((call) => registeredHandler(call) &&
|
|
18
|
+
call.arguments.some((argument) => resolver.resolve(argument, new Map())?.node === fn));
|
|
19
|
+
const callers = calls.filter((call) => {
|
|
20
|
+
if (resolver.resolve(call.expression, new Map())?.node === fn)
|
|
21
|
+
return true;
|
|
22
|
+
const owner = enclosingFunction(call);
|
|
23
|
+
if (!owner)
|
|
24
|
+
return false;
|
|
25
|
+
return calls.some((invocation) => {
|
|
26
|
+
const target = resolver.resolve(invocation.expression, new Map());
|
|
27
|
+
if (target?.node !== owner)
|
|
28
|
+
return false;
|
|
29
|
+
const env = callbackArgumentBindings(owner, invocation, new Map(), target.env, checker, resolver);
|
|
30
|
+
return !!env && resolver.resolve(call.expression, env)?.node === fn;
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return { registered, callers };
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** A helper parameter is a response context only when every executable caller passes one. */
|
|
3
|
+
export declare function httpHandlerContext(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker, active?: Set<ts.FunctionLikeDeclaration>): ts.Symbol | undefined;
|
|
4
|
+
/** Literal bracket access is equivalent to property access for response methods. */
|
|
5
|
+
export declare function contextResponseMethod(expression: ts.Expression, context: ts.Symbol, checker: ts.TypeChecker, allowMutable?: boolean): string | undefined;
|
|
6
|
+
/** Follow only caller contexts already admitted by the actual-argument proof. */
|
|
7
|
+
export declare function httpContextScopes(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker): Map<ts.FunctionLikeDeclaration, ts.Symbol>;
|
|
8
|
+
/** Indirect methods cannot prove the declared status or body, including borrowed this receivers. */
|
|
9
|
+
export declare function indirectHttpResponseMethod(call: ts.CallExpression, context: ts.Symbol, checker: ts.TypeChecker): string | undefined;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { httpContextInvocations } from './protocol-http-context-callers.mjs';
|
|
3
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
4
|
+
import { expressionReceiver } from './protocol-write-receiver.mjs';
|
|
5
|
+
import { hasBindingWrite } from './registered-route-binding-writes.mjs';
|
|
6
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
7
|
+
/** A helper parameter is a response context only when every executable caller passes one. */
|
|
8
|
+
export function httpHandlerContext(fn, checker, active = new Set()) {
|
|
9
|
+
if (active.has(fn))
|
|
10
|
+
return undefined;
|
|
11
|
+
const parameter = runtimeParameters(fn)[0];
|
|
12
|
+
const context = parameter && ts.isIdentifier(parameter.name)
|
|
13
|
+
? checker.getSymbolAtLocation(parameter.name)
|
|
14
|
+
: undefined;
|
|
15
|
+
if (!context || hasBindingWrite(parameter, checker))
|
|
16
|
+
return undefined;
|
|
17
|
+
const { registered, callers } = httpContextInvocations(fn, checker);
|
|
18
|
+
const next = new Set(active).add(fn);
|
|
19
|
+
const valid = (registered || callers.length > 0) &&
|
|
20
|
+
callers.every((call) => {
|
|
21
|
+
const argument = call.arguments[0];
|
|
22
|
+
const receiver = argument && expressionReceiver(argument, checker);
|
|
23
|
+
const declaration = receiver?.root.valueDeclaration;
|
|
24
|
+
const owner = declaration && ts.isParameter(declaration) ? enclosingFunction(declaration) : undefined;
|
|
25
|
+
return (!!owner &&
|
|
26
|
+
!!receiver &&
|
|
27
|
+
receiver.path.length === 0 &&
|
|
28
|
+
!receiver.mutableAlias &&
|
|
29
|
+
receiver.root === httpHandlerContext(owner, checker, next));
|
|
30
|
+
});
|
|
31
|
+
const result = valid ? context : undefined;
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** Literal bracket access is equivalent to property access for response methods. */
|
|
35
|
+
export function contextResponseMethod(expression, context, checker, allowMutable = false) {
|
|
36
|
+
const access = methodAccess(expression);
|
|
37
|
+
if (!access)
|
|
38
|
+
return undefined;
|
|
39
|
+
const receiver = expressionReceiver(access.receiver, checker) ??
|
|
40
|
+
bracketResponseReceiver(access.receiver, checker);
|
|
41
|
+
if (receiver?.root !== context || (receiver.mutableAlias && !allowMutable))
|
|
42
|
+
return undefined;
|
|
43
|
+
if (receiver.path.length === 0)
|
|
44
|
+
return access.name;
|
|
45
|
+
if (receiver.path.length === 1 && receiver.path[0] === 'response')
|
|
46
|
+
return `response.${access.name}`;
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
function methodAccess(expression) {
|
|
50
|
+
if (ts.isPropertyAccessExpression(expression))
|
|
51
|
+
return { receiver: expression.expression, name: expression.name.text };
|
|
52
|
+
if (ts.isElementAccessExpression(expression) && ts.isStringLiteral(expression.argumentExpression))
|
|
53
|
+
return { receiver: expression.expression, name: expression.argumentExpression.text };
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function bracketResponseReceiver(expression, checker) {
|
|
57
|
+
const access = methodAccess(expression);
|
|
58
|
+
if (!access || access.name !== 'response')
|
|
59
|
+
return undefined;
|
|
60
|
+
const receiver = expressionReceiver(access.receiver, checker);
|
|
61
|
+
return receiver && { ...receiver, path: [...receiver.path, 'response'] };
|
|
62
|
+
}
|
|
63
|
+
/** Follow only caller contexts already admitted by the actual-argument proof. */
|
|
64
|
+
export function httpContextScopes(fn, checker) {
|
|
65
|
+
const scopes = new Map();
|
|
66
|
+
const pending = [fn];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
while (pending.length) {
|
|
69
|
+
const current = pending.pop();
|
|
70
|
+
if (seen.has(current))
|
|
71
|
+
continue;
|
|
72
|
+
seen.add(current);
|
|
73
|
+
const context = httpHandlerContext(current, checker);
|
|
74
|
+
if (!context)
|
|
75
|
+
continue;
|
|
76
|
+
scopes.set(current, context);
|
|
77
|
+
for (const call of httpContextInvocations(current, checker).callers) {
|
|
78
|
+
// Admission above proves every caller's parameter-owned context.
|
|
79
|
+
const receiver = expressionReceiver(call.arguments[0], checker);
|
|
80
|
+
pending.push(enclosingFunction(receiver.root.valueDeclaration));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return scopes;
|
|
84
|
+
}
|
|
85
|
+
/** Indirect methods cannot prove the declared status or body, including borrowed this receivers. */
|
|
86
|
+
export function indirectHttpResponseMethod(call, context, checker) {
|
|
87
|
+
const outer = methodAccess(call.expression);
|
|
88
|
+
if (!outer || !['call', 'apply', 'bind'].includes(outer.name))
|
|
89
|
+
return undefined;
|
|
90
|
+
const direct = contextResponseMethod(outer.receiver, context, checker, true);
|
|
91
|
+
if (direct)
|
|
92
|
+
return direct;
|
|
93
|
+
const method = expressionReceiver(outer.receiver, checker);
|
|
94
|
+
if (method?.root === context)
|
|
95
|
+
return method.path[0] === 'response' ? method.path.slice(0, 2).join('.') : method.path[0];
|
|
96
|
+
const receiver = call.arguments[0] && expressionReceiver(call.arguments[0], checker);
|
|
97
|
+
if (receiver?.root !== context)
|
|
98
|
+
return undefined;
|
|
99
|
+
const target = methodAccess(outer.receiver);
|
|
100
|
+
const name = target?.name ?? method?.path.at(-1);
|
|
101
|
+
if (!name)
|
|
102
|
+
return undefined;
|
|
103
|
+
return receiver.path.length === 0
|
|
104
|
+
? name
|
|
105
|
+
: receiver.path.length === 1 && receiver.path[0] === 'response'
|
|
106
|
+
? `response.${name}`
|
|
107
|
+
: undefined;
|
|
108
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export { contextResponseMethod } from './protocol-http-context.mts';
|
|
3
|
+
export declare function httpEmissionKind(call: ts.CallExpression, response: ts.Symbol, context: ts.Symbol, checker: ts.TypeChecker): 'content' | 'none' | 'status' | undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
3
|
+
import { contextResponseMethod } from './protocol-http-context.mjs';
|
|
4
|
+
export { contextResponseMethod } from './protocol-http-context.mjs';
|
|
5
|
+
export function httpEmissionKind(call, response, context, checker) {
|
|
6
|
+
const methodName = contextResponseMethod(call.expression, context, checker);
|
|
7
|
+
if (methodName === 'setStatus' &&
|
|
8
|
+
call.arguments[0] &&
|
|
9
|
+
responseProperty(call.arguments[0], response, 'status', checker))
|
|
10
|
+
return 'status';
|
|
11
|
+
if (methodName === 'response.empty' && emptyBodyBranch(call, response, checker))
|
|
12
|
+
return 'none';
|
|
13
|
+
if (!(methodName === 'pipeline' || methodName === 'response.buffer'))
|
|
14
|
+
return undefined;
|
|
15
|
+
const body = call.arguments[0];
|
|
16
|
+
if (!body)
|
|
17
|
+
return undefined;
|
|
18
|
+
const expression = unwrapExpression(body);
|
|
19
|
+
if (responseProperty(expression, response, 'body', checker))
|
|
20
|
+
return 'content';
|
|
21
|
+
if (!ts.isCallExpression(expression) || expression.arguments.length > 1)
|
|
22
|
+
return undefined;
|
|
23
|
+
const method = expression.expression;
|
|
24
|
+
if (!ts.isPropertyAccessExpression(method))
|
|
25
|
+
return undefined;
|
|
26
|
+
if (expression.arguments.length === 0 &&
|
|
27
|
+
['arrayBuffer', 'text'].includes(method.name.text) &&
|
|
28
|
+
ts.isIdentifier(method.expression) &&
|
|
29
|
+
checker.getSymbolAtLocation(method.expression) === response)
|
|
30
|
+
return 'content';
|
|
31
|
+
const symbol = checker.getSymbolAtLocation(method.expression);
|
|
32
|
+
return method.name.text === 'from' &&
|
|
33
|
+
isNodeReadable(symbol) &&
|
|
34
|
+
expression.arguments[0] &&
|
|
35
|
+
responseProperty(expression.arguments[0], response, 'body', checker)
|
|
36
|
+
? 'content'
|
|
37
|
+
: undefined;
|
|
38
|
+
}
|
|
39
|
+
function responseProperty(expression, response, property, checker) {
|
|
40
|
+
const value = unwrapExpression(expression);
|
|
41
|
+
return (ts.isPropertyAccessExpression(value) &&
|
|
42
|
+
value.name.text === property &&
|
|
43
|
+
ts.isIdentifier(value.expression) &&
|
|
44
|
+
checker.getSymbolAtLocation(value.expression) === response);
|
|
45
|
+
}
|
|
46
|
+
function emptyBodyBranch(node, response, checker) {
|
|
47
|
+
let current = node;
|
|
48
|
+
while (current.parent && !ts.isFunctionLike(current.parent)) {
|
|
49
|
+
const parent = current.parent;
|
|
50
|
+
if (ts.isIfStatement(parent)) {
|
|
51
|
+
const condition = unwrapExpression(parent.expression);
|
|
52
|
+
if (parent.thenStatement === current &&
|
|
53
|
+
ts.isPrefixUnaryExpression(condition) &&
|
|
54
|
+
condition.operator === ts.SyntaxKind.ExclamationToken &&
|
|
55
|
+
responseProperty(condition.operand, response, 'body', checker))
|
|
56
|
+
return true;
|
|
57
|
+
if (parent.elseStatement === current &&
|
|
58
|
+
responseProperty(condition, response, 'body', checker))
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
current = parent;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function isNodeReadable(symbol) {
|
|
66
|
+
return !!symbol?.declarations?.some((declaration) => {
|
|
67
|
+
if (!ts.isImportSpecifier(declaration))
|
|
68
|
+
return false;
|
|
69
|
+
const imported = declaration.propertyName ?? declaration.name;
|
|
70
|
+
const statement = declaration.parent.parent.parent;
|
|
71
|
+
return (imported.text === 'Readable' &&
|
|
72
|
+
ts.isImportDeclaration(statement) &&
|
|
73
|
+
ts.isStringLiteral(statement.moduleSpecifier) &&
|
|
74
|
+
statement.moduleSpecifier.text === 'node:stream');
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RouteBinding } from './response-contract-route-analysis.mts';
|
|
2
|
+
import type { BackendResponseContract } from './response-contract-types.mts';
|
|
3
|
+
/** Only already-selected route rows are tainted; implicit-only routes use their requested key. */
|
|
4
|
+
export declare function taintRouteKeys(contracts: Map<string, BackendResponseContract>, binding: RouteBinding, key: string | undefined): string[];
|
|
5
|
+
export declare function markRouteTaint(contracts: Map<string, BackendResponseContract>, keys: readonly string[], binding: RouteBinding, source: string, mutable: boolean): void;
|
|
6
|
+
/** A direct setter is already covered by a successfully extracted matching SSE contract. */
|
|
7
|
+
export declare function isSseSetter(contracts: Map<string, BackendResponseContract>, binding: RouteBinding, method: string | undefined): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { markBufferedRouteUnavailable } from './response-contract-lenient.mjs';
|
|
2
|
+
/** Only already-selected route rows are tainted; implicit-only routes use their requested key. */
|
|
3
|
+
export function taintRouteKeys(contracts, binding, key) {
|
|
4
|
+
const keys = [...contracts]
|
|
5
|
+
.filter(([, row]) => row.method === binding.method && row.routeTemplate === binding.routeTemplate)
|
|
6
|
+
.map(([key]) => key);
|
|
7
|
+
return keys.length ? keys : key ? [key] : [];
|
|
8
|
+
}
|
|
9
|
+
export function markRouteTaint(contracts, keys, binding, source, mutable) {
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (contracts.get(key)?.unavailableReason)
|
|
12
|
+
continue;
|
|
13
|
+
markBufferedRouteUnavailable(contracts, key, binding, source);
|
|
14
|
+
if (mutable)
|
|
15
|
+
contracts.get(key).unavailableReason =
|
|
16
|
+
'route emits a response through a mutable context wrapper whose status or body is not statically determinable';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** A direct setter is already covered by a successfully extracted matching SSE contract. */
|
|
20
|
+
export function isSseSetter(contracts, binding, method) {
|
|
21
|
+
return (method === 'setStatus' &&
|
|
22
|
+
[...contracts.values()].some((row) => row.method === binding.method &&
|
|
23
|
+
row.routeTemplate === binding.routeTemplate &&
|
|
24
|
+
!row.unavailableReason &&
|
|
25
|
+
!!row.sseEvents?.length));
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { RouteBinding } from './response-contract-route-analysis.mts';
|
|
3
|
+
import type { BackendResponseContract } from './response-contract-types.mts';
|
|
4
|
+
export type PendingHttp = {
|
|
5
|
+
call: ts.CallExpression;
|
|
6
|
+
symbol: ts.Symbol;
|
|
7
|
+
keys: string[];
|
|
8
|
+
binding: RouteBinding;
|
|
9
|
+
variants: Omit<BackendResponseContract, 'method' | 'routeTemplate'>[];
|
|
10
|
+
declaredBodyKinds: ReadonlySet<'content' | 'none'>;
|
|
11
|
+
};
|
|
12
|
+
export declare function registerHttpProtocols(pending: readonly PendingHttp[], contracts: Map<string, BackendResponseContract>, checker: ts.TypeChecker, reject: (response: PendingHttp, error: unknown) => void): Set<ts.CallExpression>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { associateHttpResponse } from './protocol-http-association.mjs';
|
|
3
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
4
|
+
import { statusDominatesEmission } from './protocol-status-dominance.mjs';
|
|
5
|
+
export function registerHttpProtocols(pending, contracts, checker, reject) {
|
|
6
|
+
const covered = new Set();
|
|
7
|
+
const groups = new Map();
|
|
8
|
+
for (const response of pending) {
|
|
9
|
+
const handler = enclosingFunction(response.call);
|
|
10
|
+
const group = groups.get(handler) ?? [];
|
|
11
|
+
group.push(response);
|
|
12
|
+
groups.set(handler, group);
|
|
13
|
+
}
|
|
14
|
+
for (const group of groups.values()) {
|
|
15
|
+
if (!group.some((response) => response.keys.length))
|
|
16
|
+
continue;
|
|
17
|
+
try {
|
|
18
|
+
const siblings = new Set(group.map((response) => response.symbol));
|
|
19
|
+
const associations = new Map(group.map((response) => [
|
|
20
|
+
response,
|
|
21
|
+
associateHttpResponse(response.call, response.symbol, checker, siblings),
|
|
22
|
+
]));
|
|
23
|
+
const allStatuses = new Set([...associations.values()].flatMap((emissions) => [...emissions].filter(([, kind]) => kind === 'status').map(([call]) => call)));
|
|
24
|
+
const proven = new Set();
|
|
25
|
+
for (const [response, emissions] of associations) {
|
|
26
|
+
proveHttpEmissions(response, emissions, allStatuses);
|
|
27
|
+
for (const call of emissions.keys())
|
|
28
|
+
proven.add(call);
|
|
29
|
+
}
|
|
30
|
+
// Every sibling must prove its own emissions before any group contract is published.
|
|
31
|
+
for (const response of group)
|
|
32
|
+
response.variants.forEach((variant, index) => contracts.set(response.keys[index], { ...variant, ...response.binding }));
|
|
33
|
+
for (const call of proven)
|
|
34
|
+
covered.add(call);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
for (const response of group)
|
|
38
|
+
if (response.keys.length)
|
|
39
|
+
reject(response, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return covered;
|
|
43
|
+
}
|
|
44
|
+
function proveHttpEmissions(response, emissions, allStatuses) {
|
|
45
|
+
const statuses = new Set([...emissions].filter(([, kind]) => kind === 'status').map(([call]) => call));
|
|
46
|
+
const kinds = new Set(emissions.values());
|
|
47
|
+
const declaredBodyKinds = response.declaredBodyKinds;
|
|
48
|
+
if (!kinds.has('status') ||
|
|
49
|
+
[...declaredBodyKinds].some((kind) => !kinds.has(kind)) ||
|
|
50
|
+
[...kinds].some((kind) => kind !== 'status' && !declaredBodyKinds.has(kind)) ||
|
|
51
|
+
[...emissions].some(([call, kind]) => kind !== 'status' && !statusDominatesEmission(call, statuses, allStatuses)))
|
|
52
|
+
throw new Error('HTTP response variants require dominating status and associated body emissions');
|
|
53
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export declare function protocolMarker(expression: ts.Expression, checker: ts.TypeChecker): 'apiSseFrame' | 'apiOpenApiHttpResponse' | undefined;
|
|
3
|
+
export declare function propertyType(type: ts.Type, name: string, checker: ts.TypeChecker): ts.Type;
|
|
4
|
+
export declare function typeVariants(type: ts.Type): readonly ts.Type[];
|
|
5
|
+
export declare function stringLiterals(type: ts.Type): string[];
|
|
6
|
+
export declare function unwrapExpression(expression: ts.Expression): ts.Expression;
|
|
7
|
+
export declare function enclosingFunction(node: ts.Node): ts.FunctionLikeDeclaration | undefined;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
export function protocolMarker(expression, checker) {
|
|
3
|
+
const symbol = checker.getSymbolAtLocation(expression);
|
|
4
|
+
const resolved = symbol && (symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol);
|
|
5
|
+
const name = resolved?.name;
|
|
6
|
+
return name === 'apiSseFrame' || name === 'apiOpenApiHttpResponse' ? name : undefined;
|
|
7
|
+
}
|
|
8
|
+
export function propertyType(type, name, checker) {
|
|
9
|
+
const property = checker.getPropertyOfType(type, name);
|
|
10
|
+
const declaration = property?.valueDeclaration ?? property?.declarations?.[0];
|
|
11
|
+
if (!property || !declaration)
|
|
12
|
+
throw new Error(`Protocol contract requires ${name}`);
|
|
13
|
+
return checker.getTypeOfSymbolAtLocation(property, declaration);
|
|
14
|
+
}
|
|
15
|
+
export function typeVariants(type) {
|
|
16
|
+
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never))
|
|
17
|
+
throw new Error('Protocol contract requires concrete variants');
|
|
18
|
+
return type.isUnion() ? type.types : [type];
|
|
19
|
+
}
|
|
20
|
+
export function stringLiterals(type) {
|
|
21
|
+
return typeVariants(type).map((variant) => {
|
|
22
|
+
if (!(variant.flags & ts.TypeFlags.StringLiteral))
|
|
23
|
+
throw new Error('Protocol contract requires literal names and media types');
|
|
24
|
+
const value = variant.value;
|
|
25
|
+
if (!value || /[\r\n]/.test(value))
|
|
26
|
+
throw new Error('Protocol contract requires nonempty single-line names');
|
|
27
|
+
return value;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function unwrapExpression(expression) {
|
|
31
|
+
if (ts.isParenthesizedExpression(expression) ||
|
|
32
|
+
ts.isAsExpression(expression) ||
|
|
33
|
+
ts.isTypeAssertionExpression(expression) ||
|
|
34
|
+
ts.isNonNullExpression(expression) ||
|
|
35
|
+
ts.isAwaitExpression(expression))
|
|
36
|
+
return unwrapExpression(expression.expression);
|
|
37
|
+
return expression;
|
|
38
|
+
}
|
|
39
|
+
export function enclosingFunction(node) {
|
|
40
|
+
let current = node.parent;
|
|
41
|
+
while (current) {
|
|
42
|
+
if (ts.isArrowFunction(current) ||
|
|
43
|
+
ts.isFunctionExpression(current) ||
|
|
44
|
+
ts.isFunctionDeclaration(current) ||
|
|
45
|
+
ts.isMethodDeclaration(current))
|
|
46
|
+
return current;
|
|
47
|
+
current = current.parent;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { callbackOptionsEscape } from './protocol-callback-mutations.mjs';
|
|
3
|
+
import { isSupportedProtocolCallback } from './protocol-callback-invocation.mjs';
|
|
4
|
+
import { potentiallyExecuted } from './protocol-executable-path.mjs';
|
|
5
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
6
|
+
import { callbackArgumentBindings } from './protocol-callback-argument-bindings.mjs';
|
|
7
|
+
import { createProtocolCallbackValueResolver, isProtocolCallbackFunction as functionNode, } from './protocol-callback-values.mjs';
|
|
8
|
+
function implementedFunction(node) {
|
|
9
|
+
return functionNode(node) && !!node.body;
|
|
10
|
+
}
|
|
11
|
+
/** Proves an executable lexical path containing at least one opaque callback escape. */
|
|
12
|
+
export function opaqueProtocolCallbackPath(node, checker) {
|
|
13
|
+
if (!potentiallyExecuted(node))
|
|
14
|
+
return false;
|
|
15
|
+
const functions = [];
|
|
16
|
+
for (let fn = enclosingFunction(node); fn; fn = enclosingFunction(fn))
|
|
17
|
+
functions.push(fn);
|
|
18
|
+
if (!functions.length ||
|
|
19
|
+
functions.some((fn) => !fn.body || fn.asteriskToken || !potentiallyExecuted(fn)))
|
|
20
|
+
return false;
|
|
21
|
+
let opaque = false;
|
|
22
|
+
for (const fn of functions) {
|
|
23
|
+
if (isSupportedProtocolCallback(fn, checker))
|
|
24
|
+
continue;
|
|
25
|
+
if (!escapesToOpaque(fn, checker))
|
|
26
|
+
return false;
|
|
27
|
+
opaque = true;
|
|
28
|
+
}
|
|
29
|
+
return opaque;
|
|
30
|
+
}
|
|
31
|
+
function escapesToOpaque(callback, checker) {
|
|
32
|
+
const resolver = createProtocolCallbackValueResolver(checker);
|
|
33
|
+
function opaque(call, env) {
|
|
34
|
+
const implementation = resolver.resolve(call.expression, env);
|
|
35
|
+
if (implementation && functionNode(implementation.node) && implementation.node.body)
|
|
36
|
+
return false;
|
|
37
|
+
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
38
|
+
return !declaration || !('body' in declaration && declaration.body);
|
|
39
|
+
}
|
|
40
|
+
function executableCallsite(call) {
|
|
41
|
+
const callbackAncestors = new Set();
|
|
42
|
+
for (let fn = enclosingFunction(callback); fn; fn = enclosingFunction(fn))
|
|
43
|
+
callbackAncestors.add(fn);
|
|
44
|
+
for (let fn = enclosingFunction(call); fn; fn = enclosingFunction(fn))
|
|
45
|
+
if (!fn.body ||
|
|
46
|
+
fn.asteriskToken ||
|
|
47
|
+
!potentiallyExecuted(fn) ||
|
|
48
|
+
(!callbackAncestors.has(fn) && !isSupportedProtocolCallback(fn, checker)))
|
|
49
|
+
return false;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function scanBody(fn, env, active) {
|
|
53
|
+
const chain = new Set([...active, fn]);
|
|
54
|
+
let escaped = false;
|
|
55
|
+
function visit(node) {
|
|
56
|
+
if (escaped || !potentiallyExecuted(node))
|
|
57
|
+
return;
|
|
58
|
+
if (ts.isFunctionLike(node))
|
|
59
|
+
return;
|
|
60
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
61
|
+
const forwards = callbackOptionsEscape(node, env, callback, resolver);
|
|
62
|
+
if (forwards && opaque(node, env)) {
|
|
63
|
+
escaped = true;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const target = forwards ? resolver.resolve(node.expression, env) : undefined;
|
|
67
|
+
if (target &&
|
|
68
|
+
implementedFunction(target.node) &&
|
|
69
|
+
!target.node.asteriskToken &&
|
|
70
|
+
!chain.has(target.node)) {
|
|
71
|
+
const next = callbackArgumentBindings(target.node, node, env, target.env, checker, resolver);
|
|
72
|
+
if (!next || scanBody(target.node, next, chain))
|
|
73
|
+
escaped = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
ts.forEachChild(node, visit);
|
|
77
|
+
}
|
|
78
|
+
visit(fn.body);
|
|
79
|
+
return escaped;
|
|
80
|
+
}
|
|
81
|
+
function sourceCalls(source) {
|
|
82
|
+
let escaped = false;
|
|
83
|
+
function visit(node) {
|
|
84
|
+
if (escaped || !potentiallyExecuted(node))
|
|
85
|
+
return;
|
|
86
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
87
|
+
if (!executableCallsite(node))
|
|
88
|
+
return;
|
|
89
|
+
const forwards = callbackOptionsEscape(node, new Map(), callback, resolver);
|
|
90
|
+
if (forwards && opaque(node, new Map())) {
|
|
91
|
+
escaped = true;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const target = forwards ? resolver.resolve(node.expression, new Map()) : undefined;
|
|
95
|
+
if (target && implementedFunction(target.node) && !target.node.asteriskToken) {
|
|
96
|
+
const next = callbackArgumentBindings(target.node, node, new Map(), target.env, checker, resolver);
|
|
97
|
+
if (!next || scanBody(target.node, next, new Set()))
|
|
98
|
+
escaped = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ts.forEachChild(node, visit);
|
|
102
|
+
}
|
|
103
|
+
visit(source);
|
|
104
|
+
return escaped;
|
|
105
|
+
}
|
|
106
|
+
return sourceCalls(callback.getSourceFile());
|
|
107
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** Records the actual caller compiler's library identities, including another installation. */
|
|
3
|
+
export declare function registerPlatformCompilerLibraries(program: ts.Program): void;
|
|
4
|
+
/** Recognizes only callback APIs supplied by the compiler's standard libraries or Node typings. */
|
|
5
|
+
export declare function platformCallbackArgument(node: ts.CallExpression | ts.NewExpression, checker: ts.TypeChecker): ts.Expression | undefined;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { basename, dirname, resolve as resolvePath } from 'node:path';
|
|
2
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
3
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
4
|
+
import { writtenPlatformSymbols } from './protocol-platform-writes.mjs';
|
|
5
|
+
const standardLibrary = dirname(resolvePath(ts.getDefaultLibFilePath({})));
|
|
6
|
+
const platformWrites = new WeakMap();
|
|
7
|
+
const compilerLibraries = new WeakMap();
|
|
8
|
+
/** Records the actual caller compiler's library identities, including another installation. */
|
|
9
|
+
export function registerPlatformCompilerLibraries(program) {
|
|
10
|
+
const checker = program.getTypeChecker();
|
|
11
|
+
if (!compilerLibraries.has(checker)) {
|
|
12
|
+
const libraries = new Set(program.getSourceFiles().filter((file) => program.isSourceFileDefaultLibrary(file)));
|
|
13
|
+
compilerLibraries.set(checker, libraries);
|
|
14
|
+
platformWrites.set(checker, writtenPlatformSymbols(program.getSourceFiles(), checker, libraries));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function standardDeclaration(declaration, timer, checker) {
|
|
18
|
+
const source = declaration.getSourceFile();
|
|
19
|
+
if (!source.isDeclarationFile)
|
|
20
|
+
return false;
|
|
21
|
+
const file = resolvePath(source.fileName);
|
|
22
|
+
const libraries = compilerLibraries.get(checker);
|
|
23
|
+
if (libraries
|
|
24
|
+
? libraries.has(source)
|
|
25
|
+
: dirname(file) === standardLibrary && /^lib(?:\.[\w.-]+)?\.d\.ts$/.test(basename(file)))
|
|
26
|
+
return true;
|
|
27
|
+
return (timer &&
|
|
28
|
+
/\/node_modules\/@types\/node\/(?:web-globals\/)?timers\.d\.ts$/.test(file.replaceAll('\\', '/')));
|
|
29
|
+
}
|
|
30
|
+
function platformSymbol(expression, checker, seen = new Set()) {
|
|
31
|
+
const value = unwrapExpression(expression);
|
|
32
|
+
if (!ts.isIdentifier(value))
|
|
33
|
+
return undefined;
|
|
34
|
+
const symbol = checker.getSymbolAtLocation(value);
|
|
35
|
+
if (!symbol || seen.has(symbol))
|
|
36
|
+
return undefined;
|
|
37
|
+
seen.add(symbol);
|
|
38
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
39
|
+
const target = checker.getAliasedSymbol(symbol);
|
|
40
|
+
if (seen.has(target))
|
|
41
|
+
return undefined;
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
const declaration = symbol.valueDeclaration;
|
|
45
|
+
if (declaration &&
|
|
46
|
+
ts.isVariableDeclaration(declaration) &&
|
|
47
|
+
declaration.initializer &&
|
|
48
|
+
ts.isVariableDeclarationList(declaration.parent) &&
|
|
49
|
+
declaration.parent.flags & ts.NodeFlags.Const)
|
|
50
|
+
return platformSymbol(declaration.initializer, checker, seen);
|
|
51
|
+
return symbol;
|
|
52
|
+
}
|
|
53
|
+
/** Recognizes only callback APIs supplied by the compiler's standard libraries or Node typings. */
|
|
54
|
+
export function platformCallbackArgument(node, checker) {
|
|
55
|
+
if (ts.isCallExpression(node) && node.questionDotToken)
|
|
56
|
+
return undefined;
|
|
57
|
+
const argument = node.arguments?.[0];
|
|
58
|
+
if (!argument || ts.isSpreadElement(argument))
|
|
59
|
+
return undefined;
|
|
60
|
+
const symbol = platformSymbol(node.expression, checker);
|
|
61
|
+
if (!symbol?.declarations?.length)
|
|
62
|
+
return undefined;
|
|
63
|
+
const timer = symbol.name === 'setInterval' || symbol.name === 'setTimeout';
|
|
64
|
+
if (!(timer ? ts.isCallExpression(node) : symbol.name === 'Promise' && ts.isNewExpression(node)))
|
|
65
|
+
return undefined;
|
|
66
|
+
const writes = platformWrites.get(checker) ?? writtenPlatformSymbols([node.getSourceFile()], checker);
|
|
67
|
+
if (writes.has(symbol))
|
|
68
|
+
return undefined;
|
|
69
|
+
return symbol.declarations.every((declaration) => standardDeclaration(declaration, timer, checker))
|
|
70
|
+
? argument
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export declare function literalStrings(expression: ts.Expression | undefined, checker: ts.TypeChecker): readonly string[] | undefined;
|
|
3
|
+
export declare function globalTarget(expression: ts.Expression, checker: ts.TypeChecker, seen?: Set<ts.Symbol>): ts.Expression | undefined;
|
|
4
|
+
export declare function mutatorKind(call: ts.CallExpression, checker: ts.TypeChecker, libraries: ReadonlySet<ts.SourceFile> | undefined): 'defineProperty' | 'set' | 'assign' | undefined;
|
|
5
|
+
export declare function sourceKeys(source: ts.Expression, checker: ts.TypeChecker): readonly string[] | undefined;
|