vouchington-tooling 0.29.0 → 0.30.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 +64 -16
- 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/index.mjs +0 -3
- 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 +0 -5
- 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/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/package.json +3 -8
- 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,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;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
3
|
+
import { handlerNodes } from './registered-route-handler-analysis.mjs';
|
|
4
|
+
/** Proves a terminal 405 on the handler's own context, rather than finding a nested throw. */
|
|
5
|
+
export function isTerminal405Handler(node, checker, bindings = new Map()) {
|
|
6
|
+
if (!(ts.isArrowFunction(node) ||
|
|
7
|
+
ts.isFunctionExpression(node) ||
|
|
8
|
+
ts.isFunctionDeclaration(node) ||
|
|
9
|
+
ts.isMethodDeclaration(node)) ||
|
|
10
|
+
!node.body)
|
|
11
|
+
return false;
|
|
12
|
+
const context = runtimeParameters(node)[0]?.name;
|
|
13
|
+
const symbol = context && ts.isIdentifier(context) ? checker.getSymbolAtLocation(context) : undefined;
|
|
14
|
+
return !!symbol && terminalBody(node, symbol, checker, new Set(), true, bindings);
|
|
15
|
+
}
|
|
16
|
+
function terminalBody(node, context, checker, active, returned = true, bindings = new Map()) {
|
|
17
|
+
if (!node.body || node.asteriskToken || active.has(node))
|
|
18
|
+
return false;
|
|
19
|
+
if (node.parameters.some((parameter) => !ts.isIdentifier(parameter.name) ||
|
|
20
|
+
parameter.dotDotDotToken ||
|
|
21
|
+
(parameter.initializer && !ts.isLiteralExpression(parameter.initializer))))
|
|
22
|
+
return false;
|
|
23
|
+
const next = new Set(active).add(node);
|
|
24
|
+
if (!ts.isBlock(node.body))
|
|
25
|
+
return terminalCall(node.body, context, checker, next, returned, bindings);
|
|
26
|
+
const statements = node.body.statements;
|
|
27
|
+
const terminal = statements.at(-1);
|
|
28
|
+
if (!terminal ||
|
|
29
|
+
!statements.slice(0, -1).every((statement) => isInertStatement(statement, context, checker)))
|
|
30
|
+
return false;
|
|
31
|
+
if (ts.isExpressionStatement(terminal))
|
|
32
|
+
return terminalCall(terminal.expression, context, checker, next, false, bindings);
|
|
33
|
+
return (ts.isReturnStatement(terminal) &&
|
|
34
|
+
!!terminal.expression &&
|
|
35
|
+
terminalCall(terminal.expression, context, checker, next, returned, bindings));
|
|
36
|
+
}
|
|
37
|
+
function terminalCall(expression, context, checker, active, returned, bindings) {
|
|
38
|
+
if (ts.isAwaitExpression(expression))
|
|
39
|
+
return terminalCall(expression.expression, context, checker, active, true, bindings);
|
|
40
|
+
if (ts.isParenthesizedExpression(expression))
|
|
41
|
+
return terminalCall(expression.expression, context, checker, active, returned, bindings);
|
|
42
|
+
if (!ts.isCallExpression(expression) || expression.questionDotToken)
|
|
43
|
+
return false;
|
|
44
|
+
if (expression.arguments.some((argument) => !ts.isIdentifier(argument) && !ts.isLiteralExpression(argument)))
|
|
45
|
+
return false;
|
|
46
|
+
const callee = expression.expression;
|
|
47
|
+
if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'throw') {
|
|
48
|
+
const status = expression.arguments[0];
|
|
49
|
+
return (!callee.questionDotToken &&
|
|
50
|
+
ts.isIdentifier(callee.expression) &&
|
|
51
|
+
checker.getSymbolAtLocation(callee.expression) === context &&
|
|
52
|
+
!!status &&
|
|
53
|
+
ts.isNumericLiteral(status) &&
|
|
54
|
+
Number(status.text) === 405);
|
|
55
|
+
}
|
|
56
|
+
if (!ts.isIdentifier(callee))
|
|
57
|
+
return false;
|
|
58
|
+
const implementations = [];
|
|
59
|
+
handlerNodes(callee, checker, bindings, new Set(), true, implementations);
|
|
60
|
+
if (!returned && checker.getPropertyOfType(checker.getTypeAtLocation(expression), 'then'))
|
|
61
|
+
return false;
|
|
62
|
+
return (implementations.length > 0 &&
|
|
63
|
+
implementations.every(({ node: implementation, bindings: captured }) => {
|
|
64
|
+
if (!returned &&
|
|
65
|
+
implementation.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword))
|
|
66
|
+
return false;
|
|
67
|
+
const index = expression.arguments.findIndex((argument) => ts.isIdentifier(argument) && checker.getSymbolAtLocation(argument) === context);
|
|
68
|
+
const parameter = runtimeParameters(implementation)[index]?.name;
|
|
69
|
+
const symbol = parameter && ts.isIdentifier(parameter) ? checker.getSymbolAtLocation(parameter) : undefined;
|
|
70
|
+
return !!symbol && terminalBody(implementation, symbol, checker, active, returned, captured);
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
function isInertStatement(statement, context, checker) {
|
|
74
|
+
if (ts.isEmptyStatement(statement))
|
|
75
|
+
return true;
|
|
76
|
+
if (ts.isExpressionStatement(statement)) {
|
|
77
|
+
const expression = statement.expression;
|
|
78
|
+
if (ts.isStringLiteral(expression))
|
|
79
|
+
return true;
|
|
80
|
+
if (!ts.isCallExpression(expression) || expression.arguments.length !== 2)
|
|
81
|
+
return false;
|
|
82
|
+
const callee = expression.expression;
|
|
83
|
+
const [header, value] = expression.arguments;
|
|
84
|
+
return (ts.isPropertyAccessExpression(callee) &&
|
|
85
|
+
callee.name.text === 'set' &&
|
|
86
|
+
ts.isIdentifier(callee.expression) &&
|
|
87
|
+
checker.getSymbolAtLocation(callee.expression) === context &&
|
|
88
|
+
ts.isStringLiteral(header) &&
|
|
89
|
+
header.text.toLowerCase() === 'allow' &&
|
|
90
|
+
ts.isStringLiteral(value) &&
|
|
91
|
+
/^[A-Z]+(?:, *[A-Z]+)*$/.test(value.text));
|
|
92
|
+
}
|
|
93
|
+
if (!ts.isVariableStatement(statement))
|
|
94
|
+
return false;
|
|
95
|
+
return statement.declarationList.declarations.every((declaration) => ts.isIdentifier(declaration.name) &&
|
|
96
|
+
checker.getSymbolAtLocation(declaration.name) !== context &&
|
|
97
|
+
(!declaration.initializer || ts.isLiteralExpression(declaration.initializer)));
|
|
98
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
|
|
2
3
|
import { extractContractSchema } from '../contract-schema/index.mjs';
|
|
3
4
|
import { registerRequestRouteContract, } from './request-contract-lenient.mjs';
|
|
4
5
|
import { discoverImplicitRequestContract } from './request-contract-implicit.mjs';
|
|
@@ -14,6 +15,7 @@ import { collectHandlerBindings, enclosingRouteBinding, visit, } from './respons
|
|
|
14
15
|
* `apiNoRequestBody` suppression) so Phase 2 knows to never harvest over it.
|
|
15
16
|
*/
|
|
16
17
|
export function discoverApiRequestContracts(program, sourceFiles, requestedKeys, options) {
|
|
18
|
+
registerPlatformCompilerLibraries(program);
|
|
17
19
|
const checker = program.getTypeChecker();
|
|
18
20
|
const contracts = new Map();
|
|
19
21
|
const markerKeys = new Set();
|
|
@@ -6,11 +6,34 @@ import { isXmlResponseCall, streamingTextMediaType } from './response-contract-m
|
|
|
6
6
|
import { markBufferedRouteUnavailable, registerRouteContract, } from './response-contract-lenient.mjs';
|
|
7
7
|
import { enclosingRouteBinding, isContextMethod, isContextResponseBufferCall, isContextResponseEmptyCall, requestedKeyForBinding, responseMarker, } from './response-contract-route-analysis.mjs';
|
|
8
8
|
import { resolveEmissionStatus } from './response-contract-status.mjs';
|
|
9
|
+
import { opaqueHttpResponse, unsupportedContextResponse, supportedResponseContext, } from './protocol-http-association.mjs';
|
|
10
|
+
import { contextResponseMethod } from './protocol-http-context.mjs';
|
|
11
|
+
import { taintRouteKeys, markRouteTaint, isSseSetter } from './protocol-http-implicit-taint.mjs';
|
|
9
12
|
export function discoverImplicitContract(call, checker, sourceFile, contracts, handlerBindings, requestedKeys, options) {
|
|
10
|
-
const
|
|
13
|
+
const opaqueResponse = opaqueHttpResponse(call, checker);
|
|
14
|
+
const binding = enclosingRouteBinding(call, checker, handlerBindings, !opaqueResponse);
|
|
11
15
|
if (!binding)
|
|
12
16
|
return;
|
|
13
17
|
const key = requestedKeyForBinding(binding, requestedKeys);
|
|
18
|
+
const taintKeys = taintRouteKeys(contracts, binding, key);
|
|
19
|
+
if (!taintKeys.length)
|
|
20
|
+
return;
|
|
21
|
+
const context = supportedResponseContext(call, checker);
|
|
22
|
+
const bracketResponse = context &&
|
|
23
|
+
(ts.isElementAccessExpression(call.expression) ||
|
|
24
|
+
(ts.isPropertyAccessExpression(call.expression) &&
|
|
25
|
+
ts.isElementAccessExpression(call.expression.expression))) &&
|
|
26
|
+
contextResponseMethod(call.expression, context, checker);
|
|
27
|
+
const mutableResponse = unsupportedContextResponse(call, checker);
|
|
28
|
+
const sseSetter = isSseSetter(contracts, binding, bracketResponse || undefined);
|
|
29
|
+
if (opaqueResponse ||
|
|
30
|
+
mutableResponse ||
|
|
31
|
+
(bracketResponse && !sseSetter && !containsResponseMarker(call))) {
|
|
32
|
+
markRouteTaint(contracts, taintKeys, binding, sourceLocation(sourceFile, call), !!mutableResponse &&
|
|
33
|
+
!!context &&
|
|
34
|
+
!!contextResponseMethod(call.expression, context, checker, true));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
14
37
|
if (!key)
|
|
15
38
|
return;
|
|
16
39
|
const body = responseBodyExpression(call);
|
|
@@ -30,17 +53,13 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
|
|
|
30
53
|
mediaTypeKnowledge: 'known',
|
|
31
54
|
...status,
|
|
32
55
|
};
|
|
33
|
-
//
|
|
34
|
-
// genuine single-variant route still reports a real `unavailableReason` on failure.
|
|
56
|
+
// Keep primary extraction failures unavailable in both full and subset discovery.
|
|
35
57
|
if (requestedKeys || !contracts.has(key)) {
|
|
36
58
|
registerRouteContract(contracts, key, binding, location, emission, extract, options);
|
|
37
59
|
return;
|
|
38
60
|
}
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// `groupContractsByRoute` merges by method/routeTemplate, not this key. This route already has
|
|
42
|
-
// a primary contract, so retain this one under a deterministic variant key. A failed secondary
|
|
43
|
-
// extraction is registered as unavailable so the operation never silently omits a real branch.
|
|
61
|
+
// Preserve every additional unmarked body under a deterministic variant key, including
|
|
62
|
+
// unavailable secondary extractions. Route grouping merges these without hiding a branch.
|
|
44
63
|
registerRouteContract(contracts, nextImplicitVariantKey(contracts, key), binding, location, emission, extract, options);
|
|
45
64
|
return;
|
|
46
65
|
}
|
|
@@ -72,8 +91,7 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
|
|
|
72
91
|
}, () => extractContractSchema(checker.getStringType(), checker, location, options), options);
|
|
73
92
|
return;
|
|
74
93
|
}
|
|
75
|
-
// Empty
|
|
76
|
-
// so every buffer or pipeline without an explicit response marker makes the route unavailable.
|
|
94
|
+
// Empty branches must not hide any unmarked buffer or pipeline body.
|
|
77
95
|
if (isContextResponseBufferCall(call.expression) ||
|
|
78
96
|
(isContextMethod(call.expression, 'pipeline') && !body && !containsResponseMarker(call))) {
|
|
79
97
|
markBufferedRouteUnavailable(contracts, key, binding, sourceLocation(sourceFile, call));
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
|
|
3
|
+
import { requestedProtocolKey } from './protocol-requested-keys.mjs';
|
|
4
|
+
import { discoverProtocolContracts } from './protocol-contract-registry.mjs';
|
|
2
5
|
import { discoverImplicitContract } from './response-contract-implicit.mjs';
|
|
3
6
|
import { registerRouteContract, } from './response-contract-lenient.mjs';
|
|
4
7
|
import { binaryContentContract, contractError, extractBodyContract, noContentContract, sourceLocation, } from './response-contract-registration.mjs';
|
|
5
8
|
import { collectHandlerBindings, enclosingRouteBinding, isContextMethod, responseMarker, visit, } from './response-contract-route-analysis.mjs';
|
|
6
9
|
import { resolveEmissionStatus } from './response-contract-status.mjs';
|
|
7
10
|
export function discoverApiResponseContracts(program, sourceFiles, requestedKeys, options) {
|
|
11
|
+
registerPlatformCompilerLibraries(program);
|
|
8
12
|
const checker = program.getTypeChecker();
|
|
9
13
|
const contracts = new Map();
|
|
10
14
|
const handlerBindings = collectHandlerBindings(sourceFiles, checker);
|
|
15
|
+
const protocolContracts = new Map();
|
|
16
|
+
const protocolEmissions = discoverProtocolContracts(sourceFiles, checker, handlerBindings, protocolContracts, options, requestedKeys);
|
|
17
|
+
for (const [key, contract] of protocolContracts) {
|
|
18
|
+
const requestedKey = requestedProtocolKey(key, contract, requestedKeys);
|
|
19
|
+
if (requestedKey)
|
|
20
|
+
contracts.set(requestedKey, contract);
|
|
21
|
+
}
|
|
11
22
|
for (const sourceFile of sourceFiles) {
|
|
12
23
|
visit(sourceFile, (node) => {
|
|
13
24
|
if (!ts.isCallExpression(node))
|
|
@@ -47,7 +58,9 @@ export function discoverApiResponseContracts(program, sourceFiles, requestedKeys
|
|
|
47
58
|
}
|
|
48
59
|
for (const sourceFile of sourceFiles) {
|
|
49
60
|
visit(sourceFile, (node) => {
|
|
50
|
-
if (!ts.isCallExpression(node) ||
|
|
61
|
+
if (!ts.isCallExpression(node) ||
|
|
62
|
+
responseMarker(node.expression) ||
|
|
63
|
+
protocolEmissions.has(node))
|
|
51
64
|
return;
|
|
52
65
|
discoverImplicitContract(node, checker, sourceFile, contracts, handlerBindings, requestedKeys, options);
|
|
53
66
|
});
|
|
@@ -6,7 +6,7 @@ export type RouteBinding = {
|
|
|
6
6
|
};
|
|
7
7
|
export type HandlerBindings = Map<ts.Symbol, RouteBinding>;
|
|
8
8
|
export declare function requestedKeyForBinding(binding: RouteBinding, requestedKeys?: ReadonlySet<string>): string | undefined;
|
|
9
|
-
export declare function enclosingRouteBinding(node: ts.Node, checker: ts.TypeChecker, handlerBindings: HandlerBindings): RouteBinding | undefined;
|
|
9
|
+
export declare function enclosingRouteBinding(node: ts.Node, checker: ts.TypeChecker, handlerBindings: HandlerBindings, proveCallbacks?: boolean): RouteBinding | undefined;
|
|
10
10
|
export declare function collectHandlerBindings(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): HandlerBindings;
|
|
11
11
|
export declare function routeTemplateFromExpression(expression: ts.Expression): string | undefined;
|
|
12
12
|
export declare function propertyName(expression: ts.Expression): string | undefined;
|