vouchington-tooling 0.33.0 → 0.34.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 +181 -1
- package/dist/api-contract-discovery/index.d.mts +4 -1
- package/dist/api-contract-discovery/index.mjs +1 -0
- package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
- package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
- package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
- package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
- package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
- package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
- package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
- package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
- package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
- package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
- package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
- package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
- package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
- package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
- package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
- package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
- package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
- package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
- package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
- package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
- package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
- package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
- package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
- package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
- package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
- package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
- package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
- package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
- package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
- package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
- package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
- package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
- package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
- package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
- package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
- package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
- package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
- package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
- package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
- package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
- package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
- package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
- package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
- package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
- package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
- package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
- package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
- package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
- package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
- package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
- package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
- package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
- package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
- package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
- package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
- package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
- package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
- package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
- package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
- package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
- package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
- package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
- package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
- package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
- package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
- package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
- package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
- package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
- package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
- package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
- package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
- package/dist/compiler-build/index.d.mts +2 -0
- package/dist/compiler-build/index.mjs +1 -0
- package/dist/compiler-build/program-configuration.d.mts +9 -0
- package/dist/compiler-build/program-configuration.mjs +28 -0
- package/dist/compiler-build/program-types.d.mts +29 -0
- package/dist/compiler-build/program-types.mjs +1 -0
- package/dist/compiler-build/program.d.mts +7 -0
- package/dist/compiler-build/program.mjs +78 -0
- package/dist/contract-schema/index.d.mts +4 -0
- package/dist/contract-schema/index.mjs +2 -0
- package/dist/contract-schema/type-query-api.d.mts +44 -0
- package/dist/contract-schema/type-query-api.mjs +10 -0
- package/dist/contract-schema/type-query-call.d.mts +16 -0
- package/dist/contract-schema/type-query-call.mjs +52 -0
- package/dist/contract-schema/type-query-export.d.mts +10 -0
- package/dist/contract-schema/type-query-export.mjs +91 -0
- package/dist/contract-schema/type-query.d.mts +22 -0
- package/dist/contract-schema/type-query.mjs +39 -0
- package/dist/finite-enum-ripple/ast.d.mts +9 -0
- package/dist/finite-enum-ripple/ast.mjs +94 -0
- package/dist/finite-enum-ripple/check.d.mts +3 -0
- package/dist/finite-enum-ripple/check.mjs +62 -0
- package/dist/finite-enum-ripple/compare.d.mts +20 -0
- package/dist/finite-enum-ripple/compare.mjs +90 -0
- package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
- package/dist/finite-enum-ripple/component-paths.mjs +30 -0
- package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
- package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
- package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
- package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
- package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
- package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
- package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
- package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
- package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
- package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
- package/dist/finite-enum-ripple/index.d.mts +2 -0
- package/dist/finite-enum-ripple/index.mjs +1 -0
- package/dist/finite-enum-ripple/model.d.mts +74 -0
- package/dist/finite-enum-ripple/model.mjs +1 -0
- package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
- package/dist/finite-enum-ripple/parser-support.mjs +86 -0
- package/dist/finite-enum-ripple/parsers.d.mts +32 -0
- package/dist/finite-enum-ripple/parsers.mjs +113 -0
- package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
- package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
- package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
- package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
- package/dist/finite-enum-ripple/types.d.mts +2 -0
- package/dist/finite-enum-ripple/types.mjs +60 -0
- package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
- package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
- package/dist/finite-enum-ripple/union-types.d.mts +2 -0
- package/dist/finite-enum-ripple/union-types.mjs +129 -0
- package/dist/openapi-document/build-openapi-query.mjs +1 -1
- package/dist/openapi-document/openapi-types.d.mts +1 -1
- package/dist/openapi-document/query-types.d.mts +8 -2
- package/dist/post-publication-inventory/index.d.mts +5 -0
- package/dist/post-publication-inventory/index.mjs +2 -0
- package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
- package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
- package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
- package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
- package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
- package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
- package/dist/post-publication-inventory/writers/source.d.mts +9 -0
- package/dist/post-publication-inventory/writers/source.mjs +128 -0
- package/dist/post-publication-inventory/writers/types.d.mts +19 -0
- package/dist/post-publication-inventory/writers/types.mjs +1 -0
- package/package.json +13 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { executableProtocolPath } from './protocol-execution-path.mjs';
|
|
3
|
+
import { potentiallyExecuted } from './protocol-executable-path.mjs';
|
|
4
|
+
import { attributionSymbol, resolveSymbol } from './response-contract-symbols.mjs';
|
|
5
|
+
import { findMutatedBindings, findMutatedProperties, } from './response-contract-property-mutations.mjs';
|
|
6
|
+
import { handlerArgumentSymbols, isMutatedPropertyAccess, } from './response-contract-handler-calls.mjs';
|
|
7
|
+
import { HTTP_METHODS, propertyName, routeTemplateFromExpression, unwrapTransparentExpression, visit, } from './response-contract-route-syntax.mjs';
|
|
8
|
+
import { destructuredPropertyImplementationSymbol, propertyImplementationSymbol, } from './response-contract-object-symbols.mjs';
|
|
9
|
+
export function collectHandlerBindings(sourceFiles, checker, ambiguousBindings) {
|
|
10
|
+
const bindingsBySymbol = new Map();
|
|
11
|
+
const attributionBindingsBySymbol = new Map();
|
|
12
|
+
const mutatedProperties = ambiguousBindings
|
|
13
|
+
? findMutatedProperties(sourceFiles, checker)
|
|
14
|
+
: undefined;
|
|
15
|
+
const mutatedBindings = ambiguousBindings ? findMutatedBindings(sourceFiles, checker) : undefined;
|
|
16
|
+
for (const sourceFile of sourceFiles) {
|
|
17
|
+
visit(sourceFile, (node) => {
|
|
18
|
+
if (!ts.isCallExpression(node))
|
|
19
|
+
return;
|
|
20
|
+
const method = propertyName(node.expression)?.toUpperCase();
|
|
21
|
+
if (!method || !HTTP_METHODS.has(method))
|
|
22
|
+
return;
|
|
23
|
+
const routeTemplate = routeTemplateFromExpression(node.expression);
|
|
24
|
+
if (!routeTemplate)
|
|
25
|
+
return;
|
|
26
|
+
const binding = { method, routeTemplate };
|
|
27
|
+
for (const symbol of handlerArgumentSymbols(node, checker)) {
|
|
28
|
+
const resolved = resolveSymbol(symbol, checker);
|
|
29
|
+
const candidates = bindingsBySymbol.get(resolved) ?? [];
|
|
30
|
+
candidates.push(binding);
|
|
31
|
+
bindingsBySymbol.set(resolved, candidates);
|
|
32
|
+
}
|
|
33
|
+
if (ambiguousBindings) {
|
|
34
|
+
if (!potentiallyExecuted(node))
|
|
35
|
+
return;
|
|
36
|
+
if (!isExportedRouteBuilder(node) && !executableProtocolPath(node, checker))
|
|
37
|
+
return;
|
|
38
|
+
for (const symbol of handlerArgumentSymbols(node, checker, true, mutatedProperties)) {
|
|
39
|
+
const propertySymbol = attributionSymbol(resolveSymbol(symbol, checker), checker);
|
|
40
|
+
if (mutatedProperties?.has(propertySymbol))
|
|
41
|
+
continue;
|
|
42
|
+
const handlerSymbol = resolveHandlerSymbol(symbol, checker, mutatedProperties);
|
|
43
|
+
const resolvedAttributionSymbol = attributionSymbol(handlerSymbol, checker);
|
|
44
|
+
if (mutatedProperties?.has(resolvedAttributionSymbol))
|
|
45
|
+
continue;
|
|
46
|
+
if (mutatedBindings?.has(resolvedAttributionSymbol))
|
|
47
|
+
continue;
|
|
48
|
+
const attributionCandidates = attributionBindingsBySymbol.get(resolvedAttributionSymbol) ?? [];
|
|
49
|
+
attributionCandidates.push(binding);
|
|
50
|
+
attributionBindingsBySymbol.set(resolvedAttributionSymbol, attributionCandidates);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const bindings = new Map();
|
|
56
|
+
for (const [symbol, candidates] of bindingsBySymbol) {
|
|
57
|
+
const [first] = candidates;
|
|
58
|
+
const unambiguous = candidates.every((candidate) => candidate.method === first.method && candidate.routeTemplate === first.routeTemplate);
|
|
59
|
+
if (unambiguous) {
|
|
60
|
+
bindings.set(symbol, first);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (ambiguousBindings) {
|
|
64
|
+
for (const [symbol, candidates] of attributionBindingsBySymbol) {
|
|
65
|
+
const [first] = candidates;
|
|
66
|
+
const unambiguous = candidates.every((candidate) => candidate.method === first.method && candidate.routeTemplate === first.routeTemplate);
|
|
67
|
+
if (!unambiguous) {
|
|
68
|
+
ambiguousBindings.set(symbol, [
|
|
69
|
+
...new Set(candidates.map(({ method, routeTemplate }) => `${method}:${routeTemplate}`)),
|
|
70
|
+
].toSorted());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return bindings;
|
|
75
|
+
}
|
|
76
|
+
function isExportedRouteBuilder(node) {
|
|
77
|
+
let current = node.parent;
|
|
78
|
+
while (current) {
|
|
79
|
+
if (ts.isFunctionLike(current))
|
|
80
|
+
return (ts.isFunctionDeclaration(current) &&
|
|
81
|
+
!!(ts.getCombinedModifierFlags(current) &
|
|
82
|
+
(ts.ModifierFlags.Export | ts.ModifierFlags.Default)));
|
|
83
|
+
current = current.parent;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function resolveHandlerSymbol(symbol, checker, mutatedProperties) {
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
let current = resolveSymbol(symbol, checker);
|
|
90
|
+
while (!seen.has(current)) {
|
|
91
|
+
seen.add(current);
|
|
92
|
+
const declaration = current.valueDeclaration;
|
|
93
|
+
if (declaration && ts.isBindingElement(declaration)) {
|
|
94
|
+
const value = destructuredPropertyImplementationSymbol(declaration, checker);
|
|
95
|
+
if (!value)
|
|
96
|
+
return current;
|
|
97
|
+
current = resolveSymbol(value, checker);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (declaration && ts.isShorthandPropertyAssignment(declaration)) {
|
|
101
|
+
const value = checker.getShorthandAssignmentValueSymbol(declaration);
|
|
102
|
+
if (!value)
|
|
103
|
+
return current;
|
|
104
|
+
current = resolveSymbol(value, checker);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer)
|
|
108
|
+
return current;
|
|
109
|
+
if (!ts.isVariableDeclarationList(declaration.parent) ||
|
|
110
|
+
!(declaration.parent.flags & ts.NodeFlags.Const))
|
|
111
|
+
return current;
|
|
112
|
+
const initializer = unwrapTransparentExpression(declaration.initializer);
|
|
113
|
+
if (ts.isPropertyAccessExpression(initializer) &&
|
|
114
|
+
isMutatedPropertyAccess(initializer, checker, mutatedProperties))
|
|
115
|
+
return current;
|
|
116
|
+
const next = ts.isIdentifier(initializer)
|
|
117
|
+
? checker.getSymbolAtLocation(initializer)
|
|
118
|
+
: ts.isPropertyAccessExpression(initializer)
|
|
119
|
+
? propertyImplementationSymbol(initializer, checker)
|
|
120
|
+
: undefined;
|
|
121
|
+
if (!next)
|
|
122
|
+
return current;
|
|
123
|
+
current = resolveSymbol(next, checker);
|
|
124
|
+
}
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export declare function handlerArgumentSymbols(node: ts.CallExpression, checker: ts.TypeChecker, unwrapArguments?: boolean, mutatedProperties?: ReadonlySet<ts.Symbol>): ts.Symbol[];
|
|
3
|
+
export declare function isMutatedPropertyAccess(expression: ts.Expression, checker: ts.TypeChecker, mutatedProperties: ReadonlySet<ts.Symbol> | undefined): boolean;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { executableProtocolPath } from './protocol-execution-path.mjs';
|
|
3
|
+
import { attributionSymbol, resolveSymbol } from './response-contract-symbols.mjs';
|
|
4
|
+
import { propertyImplementationSymbol } from './response-contract-object-symbols.mjs';
|
|
5
|
+
import { isInlineRouteHandler, unwrapTransparentExpression, } from './response-contract-route-syntax.mjs';
|
|
6
|
+
export function handlerArgumentSymbols(node, checker, unwrapArguments = false, mutatedProperties) {
|
|
7
|
+
const symbols = [];
|
|
8
|
+
for (const originalArgument of node.arguments) {
|
|
9
|
+
const argument = unwrapArguments
|
|
10
|
+
? unwrapTransparentExpression(originalArgument)
|
|
11
|
+
: originalArgument;
|
|
12
|
+
if (ts.isIdentifier(argument)) {
|
|
13
|
+
const symbol = checker.getSymbolAtLocation(argument);
|
|
14
|
+
if (symbol)
|
|
15
|
+
symbols.push(symbol);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (unwrapArguments && ts.isPropertyAccessExpression(argument)) {
|
|
19
|
+
const mutatedSymbol = mutatedPropertyAccessSymbol(argument, checker, mutatedProperties);
|
|
20
|
+
if (mutatedSymbol) {
|
|
21
|
+
symbols.push(mutatedSymbol);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const symbol = propertyImplementationSymbol(argument, checker);
|
|
25
|
+
if (symbol)
|
|
26
|
+
symbols.push(symbol);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!isFunctionLike(argument))
|
|
30
|
+
continue;
|
|
31
|
+
visitHandlerCalls(argument, argument, checker, unwrapArguments, (child) => {
|
|
32
|
+
const callee = unwrapArguments
|
|
33
|
+
? unwrapTransparentExpression(child.expression)
|
|
34
|
+
: child.expression;
|
|
35
|
+
const mutatedSymbol = mutatedPropertyAccessSymbol(callee, checker, mutatedProperties);
|
|
36
|
+
if (mutatedSymbol) {
|
|
37
|
+
symbols.push(mutatedSymbol);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const symbol = handlerSymbol(callee, checker, unwrapArguments);
|
|
41
|
+
if (symbol)
|
|
42
|
+
symbols.push(symbol);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return symbols;
|
|
46
|
+
}
|
|
47
|
+
export function isMutatedPropertyAccess(expression, checker, mutatedProperties) {
|
|
48
|
+
return !!mutatedPropertyAccessSymbol(expression, checker, mutatedProperties);
|
|
49
|
+
}
|
|
50
|
+
function mutatedPropertyAccessSymbol(expression, checker, mutatedProperties) {
|
|
51
|
+
if (!mutatedProperties || !ts.isPropertyAccessExpression(expression))
|
|
52
|
+
return undefined;
|
|
53
|
+
const contextual = checker.getSymbolAtLocation(expression.name);
|
|
54
|
+
return contextual &&
|
|
55
|
+
mutatedProperties.has(attributionSymbol(resolveSymbol(contextual, checker), checker))
|
|
56
|
+
? contextual
|
|
57
|
+
: undefined;
|
|
58
|
+
}
|
|
59
|
+
function handlerSymbol(expression, checker, resolvePropertyImplementation) {
|
|
60
|
+
if (resolvePropertyImplementation && ts.isPropertyAccessExpression(expression))
|
|
61
|
+
return propertyImplementationSymbol(expression, checker);
|
|
62
|
+
return checker.getSymbolAtLocation(expression);
|
|
63
|
+
}
|
|
64
|
+
function visitHandlerCalls(node, root, checker, attributeNestedCalls, onCall) {
|
|
65
|
+
if (node !== root && isInlineRouteHandler(node))
|
|
66
|
+
return;
|
|
67
|
+
const callee = ts.isCallExpression(node)
|
|
68
|
+
? attributeNestedCalls
|
|
69
|
+
? unwrapTransparentExpression(node.expression)
|
|
70
|
+
: node.expression
|
|
71
|
+
: undefined;
|
|
72
|
+
if (ts.isCallExpression(node) &&
|
|
73
|
+
!!callee &&
|
|
74
|
+
(ts.isIdentifier(callee) || (attributeNestedCalls && ts.isPropertyAccessExpression(callee))) &&
|
|
75
|
+
executableProtocolPath(node, checker, attributeNestedCalls ? root : undefined))
|
|
76
|
+
onCall(node);
|
|
77
|
+
ts.forEachChild(node, (child) => visitHandlerCalls(child, root, checker, attributeNestedCalls, onCall));
|
|
78
|
+
}
|
|
79
|
+
function isFunctionLike(node) {
|
|
80
|
+
return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
81
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ImplicitResponseCallLabel } from './response-contract-call-classification.mts';
|
|
2
3
|
import { type DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
|
|
3
4
|
import { type HandlerBindings } from './response-contract-route-analysis.mts';
|
|
4
5
|
import type { BackendResponseContract } from './response-contract-types.mts';
|
|
5
|
-
export declare function discoverImplicitContract(call: ts.CallExpression, checker: ts.TypeChecker, sourceFile: ts.SourceFile, contracts: Map<string, BackendResponseContract>, handlerBindings: HandlerBindings, requestedKeys: ReadonlySet<string> | undefined, options: DiscoverApiResponseContractsOptions | undefined): void;
|
|
6
|
+
export declare function discoverImplicitContract(call: ts.CallExpression, checker: ts.TypeChecker, sourceFile: ts.SourceFile, contracts: Map<string, BackendResponseContract>, handlerBindings: HandlerBindings, callLabel: ImplicitResponseCallLabel | undefined, requestedKeys: ReadonlySet<string> | undefined, options: DiscoverApiResponseContractsOptions | undefined): void;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
2
|
import { extractContractSchema } from '../contract-schema/index.mjs';
|
|
3
|
-
import { containsResponseMarker, noContentContract, registerContract, responseBodyExpression, sourceLocation, } from './response-contract-registration.mjs';
|
|
4
3
|
import { isInErrorBranch } from './response-contract-error-branch.mjs';
|
|
5
|
-
import {
|
|
4
|
+
import { containsResponseMarker, noContentContract, registerContract, responseBodyExpression, sourceLocation, } from './response-contract-registration.mjs';
|
|
5
|
+
import { streamingTextMediaType } from './response-contract-media.mjs';
|
|
6
|
+
import { nextImplicitVariantKey } from './response-contract-implicit-variants.mjs';
|
|
6
7
|
import { markBufferedRouteUnavailable, registerRouteContract, } from './response-contract-lenient.mjs';
|
|
7
|
-
import { enclosingRouteBinding, isContextMethod,
|
|
8
|
+
import { enclosingRouteBinding, isContextMethod, requestedKeyForBinding, responseMarker, } from './response-contract-route-analysis.mjs';
|
|
8
9
|
import { resolveEmissionStatus } from './response-contract-status.mjs';
|
|
9
10
|
import { opaqueHttpResponse, unsupportedContextResponse, supportedResponseContext, } from './protocol-http-association.mjs';
|
|
10
11
|
import { contextResponseMethod } from './protocol-http-context.mjs';
|
|
11
12
|
import { taintRouteKeys, markRouteTaint, isSseSetter } from './protocol-http-implicit-taint.mjs';
|
|
12
|
-
export function discoverImplicitContract(call, checker, sourceFile, contracts, handlerBindings, requestedKeys, options) {
|
|
13
|
+
export function discoverImplicitContract(call, checker, sourceFile, contracts, handlerBindings, callLabel, requestedKeys, options) {
|
|
13
14
|
const opaqueResponse = opaqueHttpResponse(call, checker);
|
|
14
15
|
const binding = enclosingRouteBinding(call, checker, handlerBindings, !opaqueResponse);
|
|
15
16
|
if (!binding)
|
|
@@ -63,12 +64,10 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
|
|
|
63
64
|
registerRouteContract(contracts, nextImplicitVariantKey(contracts, key), binding, location, emission, extract, options);
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
|
-
if (
|
|
67
|
+
if (callLabel === 'ctx.response.xml()') {
|
|
67
68
|
if (requestedKeys && contracts.has(key))
|
|
68
69
|
return;
|
|
69
70
|
const body = call.arguments[0];
|
|
70
|
-
if (!body)
|
|
71
|
-
return;
|
|
72
71
|
const location = sourceLocation(sourceFile, call);
|
|
73
72
|
registerRouteContract(contracts, contracts.has(key) ? nextImplicitVariantKey(contracts, key) : key, binding, location, {
|
|
74
73
|
bodyKind: 'content',
|
|
@@ -92,8 +91,8 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
|
|
|
92
91
|
return;
|
|
93
92
|
}
|
|
94
93
|
// Empty branches must not hide any unmarked buffer or pipeline body.
|
|
95
|
-
if (
|
|
96
|
-
(
|
|
94
|
+
if (callLabel === 'ctx.response.buffer()' ||
|
|
95
|
+
(callLabel === 'ctx.pipeline()' && !body && !containsResponseMarker(call))) {
|
|
97
96
|
markBufferedRouteUnavailable(contracts, key, binding, sourceLocation(sourceFile, call));
|
|
98
97
|
return;
|
|
99
98
|
}
|
|
@@ -104,7 +103,7 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
|
|
|
104
103
|
call.arguments[0] &&
|
|
105
104
|
ts.isNumericLiteral(call.arguments[0]) &&
|
|
106
105
|
(call.arguments[0].text === '204' || call.arguments[0].text === '205')) ||
|
|
107
|
-
|
|
106
|
+
callLabel === 'ctx.response.empty()';
|
|
108
107
|
if (isExplicitNoContentCall) {
|
|
109
108
|
registerContract(contracts, contracts.has(key) ? nextImplicitVariantKey(contracts, key) : key, {
|
|
110
109
|
...noContentContract(sourceLocation(sourceFile, call)),
|
|
@@ -119,10 +118,3 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
|
|
|
119
118
|
});
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
|
-
/** Picks the next deterministic registration key for another implicit response variant. */
|
|
123
|
-
function nextImplicitVariantKey(contracts, key) {
|
|
124
|
-
let suffix = 2;
|
|
125
|
-
while (contracts.has(`${key}#implicit-${suffix}`))
|
|
126
|
-
suffix++;
|
|
127
|
-
return `${key}#implicit-${suffix}`;
|
|
128
|
-
}
|
|
@@ -7,8 +7,26 @@ export type RouteDiscoveryError = {
|
|
|
7
7
|
source: string;
|
|
8
8
|
reason: string;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* A response emitter found in a named handler registered for multiple routes. `sourceLocation`
|
|
12
|
+
* is a normalized source path followed by one-based line and column; `routes` contains unique,
|
|
13
|
+
* lexically sorted `METHOD:path` keys. `label` identifies the implicit response form.
|
|
14
|
+
*/
|
|
15
|
+
export type AmbiguousAttributionFact = {
|
|
16
|
+
sourceLocation: string;
|
|
17
|
+
label: string;
|
|
18
|
+
routes: readonly string[];
|
|
19
|
+
};
|
|
10
20
|
export type DiscoverApiResponseContractsOptions = ExtractContractSchemaOptions & {
|
|
11
21
|
onRouteError?: (error: RouteDiscoveryError) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Opt in to response-attribution facts from the existing discovery pass. Facts are delivered
|
|
24
|
+
* after discovery succeeds, ordered by normalized source location; each callback receives its
|
|
25
|
+
* own copy of the sorted route list. Dynamic error payloads paired with a non-literal status are
|
|
26
|
+
* excluded from these facts; discovery output keeps its existing behavior. Consumers choose
|
|
27
|
+
* whether a fact needs a diagnostic or policy.
|
|
28
|
+
*/
|
|
29
|
+
onAmbiguousAttribution?: (fact: AmbiguousAttributionFact) => void;
|
|
12
30
|
};
|
|
13
31
|
/** Marks `key` unavailable regardless of AST visit order, for a route that can send a raw buffered body (e.g. a proxied MCP response) alongside a no-content branch. */
|
|
14
32
|
export declare function markBufferedRouteUnavailable(contracts: Map<string, BackendResponseContract>, key: string, binding: RouteBinding, source: string): void;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export declare function propertyImplementationSymbol(access: ts.PropertyAccessExpression, checker: ts.TypeChecker): ts.Symbol | undefined;
|
|
3
|
+
export declare function destructuredPropertyImplementationSymbol(declaration: ts.BindingElement, checker: ts.TypeChecker): ts.Symbol | undefined;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
|
|
3
|
+
export function propertyImplementationSymbol(access, checker) {
|
|
4
|
+
const contextual = checker.getSymbolAtLocation(access.name);
|
|
5
|
+
const receiver = unwrapTransparentExpression(access.expression);
|
|
6
|
+
if (!contextual || !ts.isIdentifier(receiver))
|
|
7
|
+
return undefined;
|
|
8
|
+
const receiverSymbol = checker.getSymbolAtLocation(receiver);
|
|
9
|
+
const declaration = receiverSymbol?.valueDeclaration;
|
|
10
|
+
if (!declaration || !ts.isVariableDeclaration(declaration))
|
|
11
|
+
return contextual;
|
|
12
|
+
if (!declaration.initializer ||
|
|
13
|
+
!ts.isVariableDeclarationList(declaration.parent) ||
|
|
14
|
+
!(declaration.parent.flags & ts.NodeFlags.Const))
|
|
15
|
+
return undefined;
|
|
16
|
+
return constObjectPropertySymbol(receiver, access.name.text, checker);
|
|
17
|
+
}
|
|
18
|
+
export function destructuredPropertyImplementationSymbol(declaration, checker) {
|
|
19
|
+
const pattern = declaration.parent;
|
|
20
|
+
if (!ts.isObjectBindingPattern(pattern) || !ts.isIdentifier(declaration.name))
|
|
21
|
+
return undefined;
|
|
22
|
+
const variable = pattern.parent;
|
|
23
|
+
if (!ts.isVariableDeclaration(variable) ||
|
|
24
|
+
!variable.initializer ||
|
|
25
|
+
!ts.isVariableDeclarationList(variable.parent) ||
|
|
26
|
+
!(variable.parent.flags & ts.NodeFlags.Const))
|
|
27
|
+
return undefined;
|
|
28
|
+
if (declaration.dotDotDotToken || declaration.initializer)
|
|
29
|
+
return undefined;
|
|
30
|
+
const property = declaration.propertyName ?? declaration.name;
|
|
31
|
+
if (!ts.isIdentifier(property) && !ts.isStringLiteral(property))
|
|
32
|
+
return undefined;
|
|
33
|
+
const initializer = unwrapTransparentExpression(variable.initializer);
|
|
34
|
+
return ts.isIdentifier(initializer)
|
|
35
|
+
? constObjectPropertySymbol(initializer, property.text, checker)
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
function constObjectPropertySymbol(receiver, key, checker, seen = new Set()) {
|
|
39
|
+
const receiverSymbol = checker.getSymbolAtLocation(receiver);
|
|
40
|
+
if (!receiverSymbol || seen.has(receiverSymbol))
|
|
41
|
+
return undefined;
|
|
42
|
+
seen.add(receiverSymbol);
|
|
43
|
+
const declaration = receiverSymbol.valueDeclaration;
|
|
44
|
+
if (!declaration ||
|
|
45
|
+
!ts.isVariableDeclaration(declaration) ||
|
|
46
|
+
!declaration.initializer ||
|
|
47
|
+
!ts.isVariableDeclarationList(declaration.parent) ||
|
|
48
|
+
!(declaration.parent.flags & ts.NodeFlags.Const))
|
|
49
|
+
return undefined;
|
|
50
|
+
const initializer = unwrapTransparentExpression(declaration.initializer);
|
|
51
|
+
if (ts.isIdentifier(initializer))
|
|
52
|
+
return constObjectPropertySymbol(initializer, key, checker, seen);
|
|
53
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
54
|
+
return undefined;
|
|
55
|
+
if (initializer.properties.some(ts.isSpreadAssignment))
|
|
56
|
+
return undefined;
|
|
57
|
+
const implementation = initializer.properties.findLast((property) => {
|
|
58
|
+
const name = property.name;
|
|
59
|
+
return objectLiteralPropertyName(name) === key;
|
|
60
|
+
});
|
|
61
|
+
if (implementation && ts.isPropertyAssignment(implementation)) {
|
|
62
|
+
const value = unwrapTransparentExpression(implementation.initializer);
|
|
63
|
+
if (ts.isIdentifier(value))
|
|
64
|
+
return checker.getSymbolAtLocation(value);
|
|
65
|
+
}
|
|
66
|
+
if (implementation &&
|
|
67
|
+
(ts.isGetAccessorDeclaration(implementation) || ts.isSetAccessorDeclaration(implementation)))
|
|
68
|
+
return undefined;
|
|
69
|
+
return implementation?.name && checker.getSymbolAtLocation(implementation.name);
|
|
70
|
+
}
|
|
71
|
+
function objectLiteralPropertyName(name) {
|
|
72
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
|
|
73
|
+
return name.text;
|
|
74
|
+
if (!ts.isComputedPropertyName(name))
|
|
75
|
+
return undefined;
|
|
76
|
+
const expression = unwrapTransparentExpression(name.expression);
|
|
77
|
+
return ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)
|
|
78
|
+
? expression.text
|
|
79
|
+
: undefined;
|
|
80
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** Direct syntactic property writes are excluded; indirect mutation is outside this proof. */
|
|
3
|
+
export declare function findMutatedProperties(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): Set<ts.Symbol>;
|
|
4
|
+
/** Direct identifier and destructuring writes are excluded; indirect writes are unproven. */
|
|
5
|
+
export declare function findMutatedBindings(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): Set<ts.Symbol>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { attributionSymbol, resolveSymbol } from './response-contract-symbols.mjs';
|
|
3
|
+
import { unwrapTransparentExpression, visit } from './response-contract-route-syntax.mjs';
|
|
4
|
+
/** Direct syntactic property writes are excluded; indirect mutation is outside this proof. */
|
|
5
|
+
export function findMutatedProperties(sourceFiles, checker) {
|
|
6
|
+
const mutated = new Set();
|
|
7
|
+
for (const sourceFile of sourceFiles) {
|
|
8
|
+
visit(sourceFile, (node) => {
|
|
9
|
+
let access;
|
|
10
|
+
if (ts.isBinaryExpression(node) &&
|
|
11
|
+
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
12
|
+
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment &&
|
|
13
|
+
ts.isPropertyAccessExpression(unwrapTransparentExpression(node.left)))
|
|
14
|
+
access = unwrapTransparentExpression(node.left);
|
|
15
|
+
else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
16
|
+
(node.operator === ts.SyntaxKind.PlusPlusToken ||
|
|
17
|
+
node.operator === ts.SyntaxKind.MinusMinusToken) &&
|
|
18
|
+
ts.isPropertyAccessExpression(node.operand))
|
|
19
|
+
access = node.operand;
|
|
20
|
+
if (!access)
|
|
21
|
+
return;
|
|
22
|
+
const symbol = checker.getSymbolAtLocation(access.name);
|
|
23
|
+
if (symbol)
|
|
24
|
+
mutated.add(attributionSymbol(resolveSymbol(symbol, checker), checker));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return mutated;
|
|
28
|
+
}
|
|
29
|
+
/** Direct identifier and destructuring writes are excluded; indirect writes are unproven. */
|
|
30
|
+
export function findMutatedBindings(sourceFiles, checker) {
|
|
31
|
+
const mutated = new Set();
|
|
32
|
+
for (const sourceFile of sourceFiles) {
|
|
33
|
+
visit(sourceFile, (node) => {
|
|
34
|
+
let identifiers = [];
|
|
35
|
+
if (ts.isBinaryExpression(node) &&
|
|
36
|
+
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
37
|
+
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment &&
|
|
38
|
+
isAssignmentTarget(node.left))
|
|
39
|
+
identifiers = assignmentTargetIdentifiers(node.left);
|
|
40
|
+
else if ((ts.isForInStatement(node) || ts.isForOfStatement(node)) &&
|
|
41
|
+
!ts.isVariableDeclarationList(node.initializer) &&
|
|
42
|
+
isAssignmentTarget(node.initializer))
|
|
43
|
+
identifiers = assignmentTargetIdentifiers(node.initializer);
|
|
44
|
+
else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
45
|
+
(node.operator === ts.SyntaxKind.PlusPlusToken ||
|
|
46
|
+
node.operator === ts.SyntaxKind.MinusMinusToken) &&
|
|
47
|
+
ts.isIdentifier(node.operand))
|
|
48
|
+
identifiers = [node.operand];
|
|
49
|
+
for (const identifier of identifiers) {
|
|
50
|
+
const symbol = checker.getSymbolAtLocation(identifier);
|
|
51
|
+
if (symbol)
|
|
52
|
+
mutated.add(attributionSymbol(resolveSymbol(symbol, checker), checker));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return mutated;
|
|
57
|
+
}
|
|
58
|
+
function isAssignmentTarget(expression) {
|
|
59
|
+
const target = unwrapTransparentExpression(expression);
|
|
60
|
+
return (ts.isIdentifier(target) ||
|
|
61
|
+
ts.isArrayLiteralExpression(target) ||
|
|
62
|
+
ts.isObjectLiteralExpression(target));
|
|
63
|
+
}
|
|
64
|
+
function assignmentTargetIdentifiers(expression) {
|
|
65
|
+
const target = unwrapTransparentExpression(expression);
|
|
66
|
+
if (ts.isIdentifier(target))
|
|
67
|
+
return [target];
|
|
68
|
+
if (ts.isArrayLiteralExpression(target))
|
|
69
|
+
return target.elements.flatMap((element) => ts.isOmittedExpression(element)
|
|
70
|
+
? []
|
|
71
|
+
: assignmentTargetIdentifiers(ts.isSpreadElement(element) ? element.expression : element));
|
|
72
|
+
if (!ts.isObjectLiteralExpression(target))
|
|
73
|
+
return [];
|
|
74
|
+
const identifiers = [];
|
|
75
|
+
for (const property of target.properties) {
|
|
76
|
+
if (ts.isShorthandPropertyAssignment(property))
|
|
77
|
+
identifiers.push(property.name);
|
|
78
|
+
else if (ts.isPropertyAssignment(property))
|
|
79
|
+
identifiers.push(...assignmentTargetIdentifiers(property.initializer));
|
|
80
|
+
else if (ts.isSpreadAssignment(property))
|
|
81
|
+
identifiers.push(...assignmentTargetIdentifiers(property.expression));
|
|
82
|
+
}
|
|
83
|
+
return identifiers;
|
|
84
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
2
|
import type { ExtractedResponseContract, ExtractContractSchemaOptions } from '../contract-schema/index.mts';
|
|
3
3
|
import type { BackendResponseContract } from './response-contract-types.mts';
|
|
4
|
-
export declare function responseBodyExpression(call: ts.CallExpression): ts.Expression | undefined;
|
|
4
|
+
export declare function responseBodyExpression(call: ts.CallExpression, unwrapPipelineArguments?: boolean): ts.Expression | undefined;
|
|
5
5
|
export declare function containsResponseMarker(node: ts.Node): boolean;
|
|
6
6
|
export declare function extractBodyContract(call: ts.CallExpression, checker: ts.TypeChecker, sourceFile: ts.SourceFile, location: string, options?: ExtractContractSchemaOptions): ExtractedResponseContract;
|
|
7
7
|
export declare function registerContract(contracts: Map<string, BackendResponseContract>, key: string, contract: BackendResponseContract): void;
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
2
|
import { extractContractSchema, hashContractSchema } from '../contract-schema/index.mjs';
|
|
3
3
|
import { isContextMethod, responseMarker, visit } from './response-contract-route-analysis.mjs';
|
|
4
|
-
|
|
4
|
+
import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
|
|
5
|
+
export function responseBodyExpression(call, unwrapPipelineArguments = false) {
|
|
5
6
|
if (isContextMethod(call.expression, 'json'))
|
|
6
7
|
return call.arguments[0];
|
|
7
8
|
if (!isContextMethod(call.expression, 'pipeline'))
|
|
8
9
|
return undefined;
|
|
9
|
-
const
|
|
10
|
+
const originalBody = call.arguments[0];
|
|
11
|
+
const pipelineBody = originalBody &&
|
|
12
|
+
(unwrapPipelineArguments ? unwrapTransparentExpression(originalBody) : originalBody);
|
|
10
13
|
if (!pipelineBody ||
|
|
11
14
|
!ts.isCallExpression(pipelineBody) ||
|
|
12
15
|
!ts.isIdentifier(pipelineBody.expression) ||
|
|
13
16
|
pipelineBody.expression.text !== 'streamJsonObject') {
|
|
14
17
|
return undefined;
|
|
15
18
|
}
|
|
16
|
-
|
|
19
|
+
const body = pipelineBody.arguments[0];
|
|
20
|
+
return body && (unwrapPipelineArguments ? unwrapTransparentExpression(body) : body);
|
|
17
21
|
}
|
|
18
22
|
export function containsResponseMarker(node) {
|
|
19
23
|
let found = false;
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
2
|
import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
|
|
3
|
-
import { requestedProtocolKey } from './protocol-requested-keys.mjs';
|
|
3
|
+
import { protocolBindingRequested, requestedProtocolKey } from './protocol-requested-keys.mjs';
|
|
4
4
|
import { discoverProtocolContracts } from './protocol-contract-registry.mjs';
|
|
5
5
|
import { discoverImplicitContract } from './response-contract-implicit.mjs';
|
|
6
|
+
import { ambiguousRoutesForCall } from './response-contract-attribution.mjs';
|
|
7
|
+
import { sortAttributionFacts } from './response-contract-attribution-facts.mjs';
|
|
8
|
+
import { implicitResponseCallLabel } from './response-contract-call-classification.mjs';
|
|
9
|
+
import { collectHandlerBindings } from './response-contract-handler-bindings.mjs';
|
|
6
10
|
import { registerRouteContract, } from './response-contract-lenient.mjs';
|
|
7
11
|
import { binaryContentContract, contractError, extractBodyContract, noContentContract, sourceLocation, } from './response-contract-registration.mjs';
|
|
8
|
-
import {
|
|
12
|
+
import { enclosingRouteBinding, isContextMethod, responseMarker, visit, } from './response-contract-route-analysis.mjs';
|
|
9
13
|
import { resolveEmissionStatus } from './response-contract-status.mjs';
|
|
10
14
|
export function discoverApiResponseContracts(program, sourceFiles, requestedKeys, options) {
|
|
11
15
|
registerPlatformCompilerLibraries(program);
|
|
12
16
|
const checker = program.getTypeChecker();
|
|
13
17
|
const contracts = new Map();
|
|
14
|
-
const
|
|
18
|
+
const ambiguousBindings = options?.onAmbiguousAttribution
|
|
19
|
+
? new Map()
|
|
20
|
+
: undefined;
|
|
21
|
+
const handlerBindings = collectHandlerBindings(sourceFiles, checker, ambiguousBindings);
|
|
22
|
+
const attributionFacts = [];
|
|
15
23
|
const protocolContracts = new Map();
|
|
16
24
|
const protocolEmissions = discoverProtocolContracts(sourceFiles, checker, handlerBindings, protocolContracts, options, requestedKeys);
|
|
17
25
|
for (const [key, contract] of protocolContracts) {
|
|
@@ -58,14 +66,41 @@ export function discoverApiResponseContracts(program, sourceFiles, requestedKeys
|
|
|
58
66
|
}
|
|
59
67
|
for (const sourceFile of sourceFiles) {
|
|
60
68
|
visit(sourceFile, (node) => {
|
|
61
|
-
if (!ts.isCallExpression(node)
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
if (!ts.isCallExpression(node))
|
|
70
|
+
return;
|
|
71
|
+
const callLabel = implicitResponseCallLabel(node);
|
|
72
|
+
if (options?.onAmbiguousAttribution) {
|
|
73
|
+
const attributionLabel = implicitResponseCallLabel(node, true, checker);
|
|
74
|
+
const routes = attributionLabel
|
|
75
|
+
? ambiguousBindings &&
|
|
76
|
+
ambiguousRoutesForCall(node, checker, handlerBindings, ambiguousBindings)
|
|
77
|
+
: undefined;
|
|
78
|
+
const requestedRoute = !requestedKeys ||
|
|
79
|
+
routes?.some((route) => {
|
|
80
|
+
const separator = route.indexOf(':');
|
|
81
|
+
return (separator > 0 &&
|
|
82
|
+
protocolBindingRequested({ method: route.slice(0, separator), routeTemplate: route.slice(separator + 1) }, requestedKeys));
|
|
83
|
+
});
|
|
84
|
+
if (attributionLabel && routes && requestedRoute) {
|
|
85
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
86
|
+
attributionFacts.push({
|
|
87
|
+
sourceLocation: `${sourceLocation(sourceFile, node)}:${position.line + 1}:${position.character + 1}`,
|
|
88
|
+
label: attributionLabel,
|
|
89
|
+
routes: [...routes],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (responseMarker(node.expression) || protocolEmissions.has(node))
|
|
64
94
|
return;
|
|
65
|
-
discoverImplicitContract(node, checker, sourceFile, contracts, handlerBindings, requestedKeys, options);
|
|
95
|
+
discoverImplicitContract(node, checker, sourceFile, contracts, handlerBindings, callLabel, requestedKeys, options);
|
|
66
96
|
});
|
|
67
97
|
}
|
|
68
|
-
|
|
98
|
+
const result = Object.fromEntries([...contracts.entries()].toSorted(([left], [right]) => left.localeCompare(right)));
|
|
99
|
+
if (options?.onAmbiguousAttribution) {
|
|
100
|
+
for (const fact of sortAttributionFacts(attributionFacts))
|
|
101
|
+
options.onAmbiguousAttribution(fact);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
69
104
|
}
|
|
70
105
|
function rawResponseMediaType(marker, node, sourceFile) {
|
|
71
106
|
if (marker !== 'apiOpenApiRawResponse')
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export { collectHandlerBindings } from './response-contract-handler-bindings.mts';
|
|
3
|
+
export { propertyName, routeTemplateFromExpression, visit, } from './response-contract-route-syntax.mts';
|
|
2
4
|
export { isContextMethod, isContextResponseBufferCall, isContextResponseEmptyCall, responseMarker, } from './response-contract-ctx-analysis.mts';
|
|
3
5
|
export type RouteBinding = {
|
|
4
6
|
method: string;
|
|
5
7
|
routeTemplate: string;
|
|
6
8
|
};
|
|
7
9
|
export type HandlerBindings = Map<ts.Symbol, RouteBinding>;
|
|
10
|
+
export type AmbiguousHandlerBindings = Map<ts.Symbol, readonly string[]>;
|
|
8
11
|
export declare function requestedKeyForBinding(binding: RouteBinding, requestedKeys?: ReadonlySet<string>): string | undefined;
|
|
9
12
|
export declare function enclosingRouteBinding(node: ts.Node, checker: ts.TypeChecker, handlerBindings: HandlerBindings, proveCallbacks?: boolean): RouteBinding | undefined;
|
|
10
|
-
export declare function collectHandlerBindings(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): HandlerBindings;
|
|
11
|
-
export declare function routeTemplateFromExpression(expression: ts.Expression): string | undefined;
|
|
12
|
-
export declare function propertyName(expression: ts.Expression): string | undefined;
|
|
13
|
-
export declare function visit(node: ts.Node, callback: (node: ts.Node) => void): void;
|