vouchington-tooling 0.32.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 +219 -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-association.mjs +8 -0
- 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/protocol-http-context-escapes.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-context-escapes.mjs +95 -0
- package/dist/api-contract-discovery/protocol-http-context.d.mts +1 -2
- package/dist/api-contract-discovery/protocol-http-context.mjs +6 -29
- package/dist/api-contract-discovery/protocol-http-method-access.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-http-method-access.mjs +34 -0
- package/dist/api-contract-discovery/protocol-http-reflect.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-http-reflect.mjs +45 -0
- package/dist/api-contract-discovery/protocol-http-response-mutations.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-response-mutations.mjs +31 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +2 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +4 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +36 -7
- package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +1 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +21 -5
- package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +1 -1
- package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +8 -2
- 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/basic-auth-doc-sync/index.d.mts +3 -0
- package/dist/basic-auth-doc-sync/index.mjs +2 -0
- package/dist/basic-auth-doc-sync/methods.d.mts +1 -0
- package/dist/basic-auth-doc-sync/methods.mjs +4 -0
- package/dist/basic-auth-doc-sync/runbook.d.mts +5 -0
- package/dist/basic-auth-doc-sync/runbook.mjs +59 -0
- package/dist/basic-auth-doc-sync/source.d.mts +2 -0
- package/dist/basic-auth-doc-sync/source.mjs +71 -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/markdown/content-facts.d.mts +6 -0
- package/dist/markdown/content-facts.mjs +18 -0
- package/dist/markdown/html-blocks.d.mts +5 -0
- package/dist/markdown/html-blocks.mjs +51 -0
- package/dist/markdown/index.d.mts +4 -1
- package/dist/markdown/index.mjs +3 -0
- package/dist/markdown/tasks.d.mts +2 -0
- package/dist/markdown/tasks.mjs +10 -0
- package/dist/markdown/types.d.mts +27 -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/docs/basic-auth-doc-sync.md +37 -0
- package/package.json +24 -3
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
2
|
import { returnedExpressions, returnsOnEveryPath } from './registered-route-factory-returns.mjs';
|
|
3
3
|
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
4
|
+
import { potentiallyExecuted } from './protocol-executable-path.mjs';
|
|
4
5
|
import { hasBindingWrite } from './registered-route-binding-writes.mjs';
|
|
5
|
-
|
|
6
|
+
import { hasStableBinding, unwrapHandlerExpression } from './registered-route-handler-helpers.mjs';
|
|
7
|
+
import { isConditionalPosition } from './request-validation-conditional.mjs';
|
|
8
|
+
export function handlerNodes(argument, checker, parameterBindings = new Map(), active = new Set(), staticProof = false, proofs,
|
|
9
|
+
/** Ignore `let`, `var` and reassigned bindings; always on under `staticProof`. */
|
|
10
|
+
stableBindings = staticProof,
|
|
11
|
+
/** Ignore returned values on statically dead paths, and mark runtime-branch ones conditional. */
|
|
12
|
+
executableReturns = false) {
|
|
13
|
+
const again = (node, bindings = parameterBindings, seen = active, sink = proofs) => handlerNodes(node, checker, bindings, seen, staticProof, sink, stableBindings, executableReturns);
|
|
6
14
|
const unwrapped = unwrapHandlerExpression(argument);
|
|
7
15
|
if (unwrapped !== argument)
|
|
8
|
-
return
|
|
16
|
+
return again(unwrapped);
|
|
9
17
|
let bound = argument;
|
|
10
18
|
const boundIdentifiers = new Set();
|
|
11
19
|
while (ts.isIdentifier(bound)) {
|
|
@@ -19,7 +27,7 @@ export function handlerNodes(argument, checker, parameterBindings = new Map(), a
|
|
|
19
27
|
bound = nextArgument;
|
|
20
28
|
}
|
|
21
29
|
if (bound !== argument)
|
|
22
|
-
return
|
|
30
|
+
return again(bound);
|
|
23
31
|
if (active.has(argument))
|
|
24
32
|
return [];
|
|
25
33
|
const next = new Set(active).add(argument);
|
|
@@ -29,7 +37,7 @@ export function handlerNodes(argument, checker, parameterBindings = new Map(), a
|
|
|
29
37
|
}
|
|
30
38
|
if (ts.isCallExpression(argument)) {
|
|
31
39
|
const resolvedProofs = [];
|
|
32
|
-
const groups = callableImplementations(argument.expression, checker, staticProof).map((implementation) => {
|
|
40
|
+
const groups = callableImplementations(argument.expression, checker, staticProof, stableBindings).map((implementation) => {
|
|
33
41
|
if (staticProof &&
|
|
34
42
|
(implementation.asteriskToken ||
|
|
35
43
|
implementation.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
@@ -42,11 +50,18 @@ export function handlerNodes(argument, checker, parameterBindings = new Map(), a
|
|
|
42
50
|
if (callArgument && symbol)
|
|
43
51
|
bindings.set(symbol, callArgument);
|
|
44
52
|
});
|
|
45
|
-
const returned = returnedExpressions(implementation);
|
|
53
|
+
const returned = returnedExpressions(implementation).filter((value) => !executableReturns || !value || potentiallyExecuted(value));
|
|
46
54
|
if (staticProof &&
|
|
47
55
|
(!returnsOnEveryPath(implementation.body) || returned.some((value) => !value)))
|
|
48
56
|
return [];
|
|
49
|
-
const results = returned.map((value) =>
|
|
57
|
+
const results = returned.map((value) => {
|
|
58
|
+
const selected = [];
|
|
59
|
+
const found = value ? again(value, bindings, next, selected) : [];
|
|
60
|
+
if (executableReturns && value && isConditionalPosition(value))
|
|
61
|
+
selected.forEach((proof) => (proof.conditional = true));
|
|
62
|
+
resolvedProofs.push(...selected);
|
|
63
|
+
return found;
|
|
64
|
+
});
|
|
50
65
|
return staticProof && results.some((result) => !result.some(ts.isFunctionLike))
|
|
51
66
|
? []
|
|
52
67
|
: results.flat();
|
|
@@ -56,16 +71,16 @@ export function handlerNodes(argument, checker, parameterBindings = new Map(), a
|
|
|
56
71
|
proofs?.push(...resolvedProofs);
|
|
57
72
|
return groups.flat();
|
|
58
73
|
}
|
|
59
|
-
return declarationImplementations(argument, checker, next, staticProof, parameterBindings, proofs);
|
|
74
|
+
return declarationImplementations(argument, checker, next, staticProof, parameterBindings, proofs, stableBindings, executableReturns);
|
|
60
75
|
}
|
|
61
|
-
function callableImplementations(node, checker, staticProof = false) {
|
|
76
|
+
function callableImplementations(node, checker, staticProof = false, stableBindings = staticProof) {
|
|
62
77
|
const symbol = checker.getSymbolAtLocation(node);
|
|
63
78
|
if (!symbol)
|
|
64
79
|
return [];
|
|
65
80
|
const resolved = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
66
81
|
const implementations = [];
|
|
67
82
|
for (const declaration of resolved.declarations ?? []) {
|
|
68
|
-
if (
|
|
83
|
+
if (stableBindings && !hasStableBinding(declaration, checker))
|
|
69
84
|
continue;
|
|
70
85
|
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
71
86
|
const initializer = unwrapHandlerExpression(declaration.initializer);
|
|
@@ -80,13 +95,13 @@ function callableImplementations(node, checker, staticProof = false) {
|
|
|
80
95
|
}
|
|
81
96
|
return implementations;
|
|
82
97
|
}
|
|
83
|
-
function declarationImplementations(node, checker, active, staticProof, parameterBindings, proofs) {
|
|
98
|
+
function declarationImplementations(node, checker, active, staticProof, parameterBindings, proofs, stableBindings = staticProof, executableReturns = false) {
|
|
84
99
|
const symbol = checker.getSymbolAtLocation(node);
|
|
85
100
|
if (!symbol)
|
|
86
101
|
return [];
|
|
87
102
|
const resolved = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
88
103
|
return (resolved.declarations ?? []).flatMap((declaration) => {
|
|
89
|
-
if (
|
|
104
|
+
if (stableBindings && !hasStableBinding(declaration, checker))
|
|
90
105
|
return [];
|
|
91
106
|
if (ts.isFunctionDeclaration(declaration) ||
|
|
92
107
|
(!staticProof && ts.isMethodDeclaration(declaration))) {
|
|
@@ -97,22 +112,8 @@ function declarationImplementations(node, checker, active, staticProof, paramete
|
|
|
97
112
|
if (ts.isVariableDeclaration(declaration) && declaration.initializer)
|
|
98
113
|
return [
|
|
99
114
|
declaration,
|
|
100
|
-
...handlerNodes(declaration.initializer, checker, parameterBindings, active, staticProof, proofs),
|
|
115
|
+
...handlerNodes(declaration.initializer, checker, parameterBindings, active, staticProof, proofs, stableBindings, executableReturns),
|
|
101
116
|
];
|
|
102
117
|
return [];
|
|
103
118
|
});
|
|
104
119
|
}
|
|
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,16 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { hasBindingWrite } from './registered-route-binding-writes.mjs';
|
|
3
|
+
export function hasStableBinding(declaration, checker) {
|
|
4
|
+
if (ts.isVariableDeclaration(declaration))
|
|
5
|
+
return (ts.isVariableDeclarationList(declaration.parent) &&
|
|
6
|
+
!!(declaration.parent.flags & ts.NodeFlags.Const));
|
|
7
|
+
return !ts.isFunctionDeclaration(declaration) || !hasBindingWrite(declaration, checker);
|
|
8
|
+
}
|
|
9
|
+
export function unwrapHandlerExpression(node) {
|
|
10
|
+
while (ts.isParenthesizedExpression(node) ||
|
|
11
|
+
ts.isAsExpression(node) ||
|
|
12
|
+
ts.isTypeAssertionExpression(node) ||
|
|
13
|
+
ts.isSatisfiesExpression(node))
|
|
14
|
+
node = node.expression;
|
|
15
|
+
return node;
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ExecutedCallbackConfig } from './request-validation-types.mts';
|
|
3
|
+
/** The listed inline callback properties of a configured executed-callback host call. */
|
|
4
|
+
export declare function executedCallbackFunctions(call: ts.CallExpression, hosts: readonly ExecutedCallbackConfig[], checker: ts.TypeChecker): ts.FunctionLikeDeclaration[];
|
|
5
|
+
/**
|
|
6
|
+
* True when some invocation of a callback runs it straight from a function the call enters, outside
|
|
7
|
+
* any condition. Invocations elsewhere, such as in nested helpers, are treated as conditional.
|
|
8
|
+
*/
|
|
9
|
+
export declare function invokedUnconditionally(callers: readonly ts.CallExpression[], runners: ReadonlySet<ts.Node>): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
3
|
+
import { isConditionalPosition } from './request-validation-conditional.mjs';
|
|
4
|
+
import { inlineFunction } from './request-validation-follow.mjs';
|
|
5
|
+
import { propertyNameText } from './request-validation-keys.mjs';
|
|
6
|
+
import { findConfig } from './request-validation-match.mjs';
|
|
7
|
+
import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
|
|
8
|
+
/** The listed inline callback properties of a configured executed-callback host call. */
|
|
9
|
+
export function executedCallbackFunctions(call, hosts, checker) {
|
|
10
|
+
const host = findConfig(call.expression, hosts, checker);
|
|
11
|
+
const argument = host && call.arguments[host.argument];
|
|
12
|
+
const object = argument && unwrapTransparentExpression(argument);
|
|
13
|
+
if (!host || !object || !ts.isObjectLiteralExpression(object))
|
|
14
|
+
return [];
|
|
15
|
+
return object.properties.flatMap((property) => {
|
|
16
|
+
const name = propertyNameText(property.name);
|
|
17
|
+
const callback = inlineFunction(ts.isPropertyAssignment(property) ? property.initializer : property);
|
|
18
|
+
return name !== undefined && host.properties.includes(name) && callback ? [callback] : [];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* True when some invocation of a callback runs it straight from a function the call enters, outside
|
|
23
|
+
* any condition. Invocations elsewhere, such as in nested helpers, are treated as conditional.
|
|
24
|
+
*/
|
|
25
|
+
export function invokedUnconditionally(callers, runners) {
|
|
26
|
+
return callers.some((caller) => !isConditionalPosition(caller) && runners.has(enclosingFunction(caller)));
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/**
|
|
3
|
+
* A checker whose symbol lookups are memoized for one analysis run. The walker resolves the same
|
|
4
|
+
* identifiers and aliases for every route that reaches a shared helper; the program is immutable,
|
|
5
|
+
* so the answers cannot change. The cache dies with the returned object.
|
|
6
|
+
*/
|
|
7
|
+
export declare function memoizeCheckerSymbols(checker: ts.TypeChecker): ts.TypeChecker;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
/** A cached lookup keyed by node or symbol; `undefined` results are cached too. */
|
|
3
|
+
function memoize(lookup) {
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
return (key) => {
|
|
6
|
+
if (cache.has(key))
|
|
7
|
+
return cache.get(key);
|
|
8
|
+
const value = lookup(key);
|
|
9
|
+
cache.set(key, value);
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A checker whose symbol lookups are memoized for one analysis run. The walker resolves the same
|
|
15
|
+
* identifiers and aliases for every route that reaches a shared helper; the program is immutable,
|
|
16
|
+
* so the answers cannot change. The cache dies with the returned object.
|
|
17
|
+
*/
|
|
18
|
+
export function memoizeCheckerSymbols(checker) {
|
|
19
|
+
return {
|
|
20
|
+
...checker,
|
|
21
|
+
getSymbolAtLocation: memoize((node) => checker.getSymbolAtLocation(node)),
|
|
22
|
+
getShorthandAssignmentValueSymbol: memoize((node) => checker.getShorthandAssignmentValueSymbol(node)),
|
|
23
|
+
getAliasedSymbol: memoize((symbol) => checker.getAliasedSymbol(symbol)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/**
|
|
3
|
+
* True when, within its own function, the node runs only under an `if` or ternary branch, the
|
|
4
|
+
* right side of `&&`, `||` or `??`, a `switch` case, a loop body, a `catch` or a `finally`. Early returns are not modeled.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isConditionalPosition(node: ts.Node): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
const SHORT_CIRCUIT = new Set([
|
|
3
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
4
|
+
ts.SyntaxKind.BarBarToken,
|
|
5
|
+
ts.SyntaxKind.QuestionQuestionToken,
|
|
6
|
+
]);
|
|
7
|
+
/**
|
|
8
|
+
* True when, within its own function, the node runs only under an `if` or ternary branch, the
|
|
9
|
+
* right side of `&&`, `||` or `??`, a `switch` case, a loop body, a `catch` or a `finally`. Early returns are not modeled.
|
|
10
|
+
*/
|
|
11
|
+
export function isConditionalPosition(node) {
|
|
12
|
+
let child = node;
|
|
13
|
+
for (let parent = node.parent; parent && !ts.isFunctionLike(parent); parent = parent.parent) {
|
|
14
|
+
if (ts.isIfStatement(parent) && child !== parent.expression)
|
|
15
|
+
return true;
|
|
16
|
+
if (ts.isConditionalExpression(parent) && child !== parent.condition)
|
|
17
|
+
return true;
|
|
18
|
+
if (ts.isBinaryExpression(parent) &&
|
|
19
|
+
child === parent.right &&
|
|
20
|
+
SHORT_CIRCUIT.has(parent.operatorToken.kind))
|
|
21
|
+
return true;
|
|
22
|
+
if (ts.isCaseOrDefaultClause(parent) || ts.isCatchClause(parent))
|
|
23
|
+
return true;
|
|
24
|
+
if (ts.isIterationStatement(parent, false) && child === parent.statement)
|
|
25
|
+
return true;
|
|
26
|
+
if (ts.isTryStatement(parent) && child === parent.finallyBlock)
|
|
27
|
+
return true;
|
|
28
|
+
child = parent;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { FactoryConfig } from './request-validation-types.mts';
|
|
3
|
+
/**
|
|
4
|
+
* The configured factory call that built the handler a call invokes: the callee resolves to a
|
|
5
|
+
* `const` binding initialized by that factory call, as in `const handler = createHandler({...})`
|
|
6
|
+
* followed by `handler(ctx)`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function calledFactory(call: ts.CallExpression, factories: readonly FactoryConfig[], checker: ts.TypeChecker): {
|
|
9
|
+
call: ts.CallExpression;
|
|
10
|
+
config: FactoryConfig;
|
|
11
|
+
} | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { calleeSymbol, findConfig } from './request-validation-match.mjs';
|
|
3
|
+
import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* The configured factory call that built the handler a call invokes: the callee resolves to a
|
|
6
|
+
* `const` binding initialized by that factory call, as in `const handler = createHandler({...})`
|
|
7
|
+
* followed by `handler(ctx)`.
|
|
8
|
+
*/
|
|
9
|
+
export function calledFactory(call, factories, checker) {
|
|
10
|
+
for (const declaration of calleeSymbol(call.expression, checker)?.declarations ?? []) {
|
|
11
|
+
if (!ts.isVariableDeclaration(declaration) || !declaration.initializer)
|
|
12
|
+
continue;
|
|
13
|
+
if (!(ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const))
|
|
14
|
+
continue;
|
|
15
|
+
const built = unwrapTransparentExpression(declaration.initializer);
|
|
16
|
+
if (!ts.isCallExpression(built))
|
|
17
|
+
continue;
|
|
18
|
+
const config = findConfig(built.expression, factories, checker);
|
|
19
|
+
if (config)
|
|
20
|
+
return { call: built, config };
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ExecutedCallbackConfig, FactoryConfig, RouteValidationFacts, ValidatorConfig } from './request-validation-types.mts';
|
|
3
|
+
export type RequestValidationFactsInput = {
|
|
4
|
+
program: ts.Program;
|
|
5
|
+
sourceFiles: readonly ts.SourceFile[];
|
|
6
|
+
validators: readonly ValidatorConfig[];
|
|
7
|
+
factories?: readonly FactoryConfig[];
|
|
8
|
+
/** Configured calls that always run the listed inline callback properties. */
|
|
9
|
+
executedCallbacks?: readonly ExecutedCallbackConfig[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Reports, per registered route, the configured validator and factory calls its handlers reach,
|
|
13
|
+
* the operation key and carriers each covers, and the raw request carriers the handlers read.
|
|
14
|
+
* Facts only: which routes must validate, and what counts as covered, stays with the caller.
|
|
15
|
+
*/
|
|
16
|
+
export declare function discoverRequestValidationFacts(input: RequestValidationFactsInput): Record<string, RouteValidationFacts>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { memoizeCheckerSymbols } from './request-validation-checker.mjs';
|
|
3
|
+
import { createProtocolCache } from './protocol-analysis-cache.mjs';
|
|
4
|
+
import { discoverRegisteredRoutes } from './registered-route-catalog.mjs';
|
|
5
|
+
import { handlerNodes } from './registered-route-handler-analysis.mjs';
|
|
6
|
+
import { insideConfiguredImplementation } from './request-validation-match.mjs';
|
|
7
|
+
import { sourceOf } from './request-validation-sites.mjs';
|
|
8
|
+
import { createRouteWalker } from './request-validation-walker.mjs';
|
|
9
|
+
import { HTTP_METHODS, propertyName, routeTemplateFromExpression, visit, } from './response-contract-route-syntax.mjs';
|
|
10
|
+
/** Maps each route's `source:method:template` identity to its registration call. */
|
|
11
|
+
function registrationCalls(sourceFiles) {
|
|
12
|
+
const calls = new Map();
|
|
13
|
+
for (const sourceFile of sourceFiles)
|
|
14
|
+
visit(sourceFile, (node) => {
|
|
15
|
+
if (!ts.isCallExpression(node))
|
|
16
|
+
return;
|
|
17
|
+
const method = propertyName(node.expression)?.toUpperCase();
|
|
18
|
+
const template = routeTemplateFromExpression(node.expression);
|
|
19
|
+
if (method && HTTP_METHODS.has(method) && template)
|
|
20
|
+
calls.set(`${sourceOf(node)}|${method}:${template}`, node);
|
|
21
|
+
});
|
|
22
|
+
return calls;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Reports, per registered route, the configured validator and factory calls its handlers reach,
|
|
26
|
+
* the operation key and carriers each covers, and the raw request carriers the handlers read.
|
|
27
|
+
* Facts only: which routes must validate, and what counts as covered, stays with the caller.
|
|
28
|
+
*/
|
|
29
|
+
export function discoverRequestValidationFacts(input) {
|
|
30
|
+
const { program, sourceFiles, validators, factories = [], executedCallbacks = [] } = input;
|
|
31
|
+
const checker = memoizeCheckerSymbols(program.getTypeChecker());
|
|
32
|
+
const registrations = registrationCalls(sourceFiles);
|
|
33
|
+
const sourceFileSet = new Set(sourceFiles);
|
|
34
|
+
const protocolCache = createProtocolCache();
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const route of discoverRegisteredRoutes(program, sourceFiles)) {
|
|
37
|
+
// Same call-selection logic as route discovery, so every discovered route has a registration.
|
|
38
|
+
const registration = registrations.get(`${route.source}|${route.method}:${route.routeTemplate}`);
|
|
39
|
+
const walker = createRouteWalker({
|
|
40
|
+
checker,
|
|
41
|
+
sourceFiles: sourceFileSet,
|
|
42
|
+
protocolCache,
|
|
43
|
+
validators,
|
|
44
|
+
factories,
|
|
45
|
+
executedCallbacks,
|
|
46
|
+
});
|
|
47
|
+
for (const argument of registration.arguments.filter((item) => !ts.isStringLiteral(item))) {
|
|
48
|
+
const proofs = [];
|
|
49
|
+
const nodes = handlerNodes(argument, checker, new Map(), new Set(), false, proofs, true, true);
|
|
50
|
+
walker.scanRegistration(argument);
|
|
51
|
+
for (const declaration of nodes.filter(ts.isVariableDeclaration))
|
|
52
|
+
walker.scanRegistration(declaration);
|
|
53
|
+
for (const proof of proofs)
|
|
54
|
+
if (!insideConfiguredImplementation(proof.node, [...validators, ...factories, ...executedCallbacks], checker))
|
|
55
|
+
walker.walkHandler(proof);
|
|
56
|
+
}
|
|
57
|
+
result[`${route.method}:${route.routeTemplate}`] = {
|
|
58
|
+
kind: route.kind,
|
|
59
|
+
source: route.source,
|
|
60
|
+
...walker.facts,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ProtocolCache } from './protocol-analysis-cache.mts';
|
|
3
|
+
import type { KeyBindings } from './request-validation-keys.mts';
|
|
4
|
+
import type { RootBindings } from './request-validation-origin.mts';
|
|
5
|
+
type ExportRef = {
|
|
6
|
+
module: string;
|
|
7
|
+
exportName: string;
|
|
8
|
+
};
|
|
9
|
+
/** Everything needed to resolve keys, origins and followed helpers at one program point. */
|
|
10
|
+
export type Scope = {
|
|
11
|
+
checker: ts.TypeChecker;
|
|
12
|
+
sourceFiles: ReadonlySet<ts.SourceFile>;
|
|
13
|
+
/** Configured validators, factories and callback hosts, whose bodies are never entered. */
|
|
14
|
+
configured: readonly ExportRef[];
|
|
15
|
+
roots: RootBindings;
|
|
16
|
+
keys: KeyBindings;
|
|
17
|
+
/** Memoized proofs shared across one facts run. */
|
|
18
|
+
cache?: ProtocolCache | undefined;
|
|
19
|
+
};
|
|
20
|
+
export type Followable = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;
|
|
21
|
+
/** Function declarations and const function expressions in `sourceFiles` that a call runs. */
|
|
22
|
+
export declare function followedImplementations(call: ts.CallExpression, scope: Scope): Followable[];
|
|
23
|
+
export declare const inlineFunction: (node: ts.Node | undefined) => ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | undefined;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { findConfig, calleeSymbol } from './request-validation-match.mjs';
|
|
3
|
+
import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
|
|
4
|
+
function constFunction(declaration) {
|
|
5
|
+
const { initializer } = declaration;
|
|
6
|
+
return initializer && ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const
|
|
7
|
+
? unwrapTransparentExpression(initializer)
|
|
8
|
+
: undefined;
|
|
9
|
+
}
|
|
10
|
+
/** Function declarations and const function expressions in `sourceFiles` that a call runs. */
|
|
11
|
+
export function followedImplementations(call, scope) {
|
|
12
|
+
if (findConfig(call.expression, scope.configured, scope.checker))
|
|
13
|
+
return [];
|
|
14
|
+
const declarations = calleeSymbol(call.expression, scope.checker)?.declarations ?? [];
|
|
15
|
+
return declarations.flatMap((declaration) => {
|
|
16
|
+
if (!scope.sourceFiles.has(declaration.getSourceFile()))
|
|
17
|
+
return [];
|
|
18
|
+
const implementation = ts.isVariableDeclaration(declaration)
|
|
19
|
+
? constFunction(declaration)
|
|
20
|
+
: declaration;
|
|
21
|
+
return implementation &&
|
|
22
|
+
(ts.isFunctionDeclaration(implementation) ||
|
|
23
|
+
ts.isFunctionExpression(implementation) ||
|
|
24
|
+
ts.isArrowFunction(implementation)) &&
|
|
25
|
+
implementation.body &&
|
|
26
|
+
// Calling a generator only creates an iterator; its body does not run.
|
|
27
|
+
!implementation.asteriskToken
|
|
28
|
+
? [implementation]
|
|
29
|
+
: [];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export const inlineFunction = (node) => {
|
|
33
|
+
const value = node && ts.isExpression(node) ? unwrapTransparentExpression(node) : node;
|
|
34
|
+
return value &&
|
|
35
|
+
(ts.isArrowFunction(value) || ts.isFunctionExpression(value) || ts.isMethodDeclaration(value))
|
|
36
|
+
? value
|
|
37
|
+
: undefined;
|
|
38
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { HandlerProof } from './registered-route-handler-analysis.mts';
|
|
3
|
+
import { type Bound } from './request-validation-origin.mts';
|
|
4
|
+
/** Static keys bound by the proof (each resolved against the earlier ones), and the handler's own context parameter as the root. */
|
|
5
|
+
export declare function handlerBindings(proof: HandlerProof, checker: ts.TypeChecker): {
|
|
6
|
+
keys: Map<ts.Symbol, string>;
|
|
7
|
+
roots: Map<ts.Symbol, Bound>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
3
|
+
import { resolveKey } from './request-validation-keys.mjs';
|
|
4
|
+
import { memberKind } from './request-validation-origin.mjs';
|
|
5
|
+
/** Binds a context parameter, or each destructured member of it, to the carrier it denotes. */
|
|
6
|
+
function bindContext(name, kind, checker, roots) {
|
|
7
|
+
if (ts.isIdentifier(name)) {
|
|
8
|
+
roots.set(checker.getSymbolAtLocation(name), { kind, origins: [] });
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (!ts.isObjectBindingPattern(name))
|
|
12
|
+
return;
|
|
13
|
+
for (const element of name.elements) {
|
|
14
|
+
const key = element.propertyName ?? element.name;
|
|
15
|
+
const member = !element.dotDotDotToken && (ts.isIdentifier(key) || ts.isStringLiteral(key))
|
|
16
|
+
? memberKind(kind, key.text)
|
|
17
|
+
: undefined;
|
|
18
|
+
if (member)
|
|
19
|
+
bindContext(element.name, member, checker, roots);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Static keys bound by the proof (each resolved against the earlier ones), and the handler's own context parameter as the root. */
|
|
23
|
+
export function handlerBindings(proof, checker) {
|
|
24
|
+
const keys = new Map();
|
|
25
|
+
for (const [symbol, expression] of proof.bindings) {
|
|
26
|
+
const key = resolveKey(expression, checker, keys);
|
|
27
|
+
if (key !== undefined)
|
|
28
|
+
keys.set(symbol, key);
|
|
29
|
+
}
|
|
30
|
+
const roots = new Map();
|
|
31
|
+
const context = runtimeParameters(proof.node)[0]?.name;
|
|
32
|
+
if (context)
|
|
33
|
+
bindContext(context, 'context', checker, roots);
|
|
34
|
+
return { keys, roots };
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { Scope } from './request-validation-follow.mts';
|
|
3
|
+
import { type ValidatorSite } from './request-validation-types.mts';
|
|
4
|
+
export type InputResolution = {
|
|
5
|
+
carriers: ValidatorSite['carriers'];
|
|
6
|
+
/** Why the input object is not fully resolved; the carriers listed may then be incomplete. */
|
|
7
|
+
unresolved?: string;
|
|
8
|
+
/** Expressions that construct the validated input, whose request reads are not raw reads. */
|
|
9
|
+
nodes: ts.Node[];
|
|
10
|
+
};
|
|
11
|
+
/** Resolves the object a validator receives as input, through const objects and spreads. */
|
|
12
|
+
export declare function resolveInput(input: ts.Expression | undefined, scope: Scope): InputResolution;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { propertyNameText, resolveObject } from './request-validation-keys.mjs';
|
|
3
|
+
import { traceValue } from './request-validation-trace.mjs';
|
|
4
|
+
import { CARRIERS } from './request-validation-types.mjs';
|
|
5
|
+
/** Applies the object's properties in order, so later properties and spreads win. */
|
|
6
|
+
function collect(object, scope, found, seen) {
|
|
7
|
+
found.nodes.push(object);
|
|
8
|
+
for (const property of object.properties) {
|
|
9
|
+
if (ts.isSpreadAssignment(property)) {
|
|
10
|
+
const spread = resolveObject(property.expression, scope.checker, scope.roots);
|
|
11
|
+
if (spread && !seen.has(spread))
|
|
12
|
+
collect(spread, scope, found, new Set(seen).add(spread));
|
|
13
|
+
else
|
|
14
|
+
found.unresolved ??= `spread \`${property.expression.getText()}\` is not statically resolvable`;
|
|
15
|
+
}
|
|
16
|
+
else if (property.name && ts.isComputedPropertyName(property.name)) {
|
|
17
|
+
found.unresolved ??= 'a computed property name is not statically resolvable';
|
|
18
|
+
}
|
|
19
|
+
else if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property)) {
|
|
20
|
+
const carrier = CARRIERS.find((item) => item === propertyNameText(property.name));
|
|
21
|
+
if (carrier)
|
|
22
|
+
found.entries.set(carrier, ts.isPropertyAssignment(property) ? property.initializer : property.name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Resolves the object a validator receives as input, through const objects and spreads. */
|
|
27
|
+
export function resolveInput(input, scope) {
|
|
28
|
+
const found = { entries: new Map(), nodes: input ? [input] : [] };
|
|
29
|
+
const object = resolveObject(input, scope.checker, scope.roots);
|
|
30
|
+
if (object)
|
|
31
|
+
collect(object, scope, found, new Set([object]));
|
|
32
|
+
else if (input)
|
|
33
|
+
found.unresolved = `input \`${input.getText()}\` is not statically resolvable`;
|
|
34
|
+
const carriers = [...found.entries].map(([carrier, value]) => {
|
|
35
|
+
const { origins, unresolved } = traceValue(value, scope);
|
|
36
|
+
return {
|
|
37
|
+
carrier,
|
|
38
|
+
origins: CARRIERS.filter((candidate) => origins.has(candidate)),
|
|
39
|
+
...(unresolved && { unresolved }),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
return { carriers, ...(found.unresolved && { unresolved: found.unresolved }), nodes: found.nodes };
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
/** Static string values bound to helper or factory parameters. */
|
|
3
|
+
export type KeyBindings = ReadonlyMap<ts.Symbol, string>;
|
|
4
|
+
/** A property name written as an identifier or string literal; computed names are `undefined`. */
|
|
5
|
+
export declare const propertyNameText: (name: ts.PropertyName | undefined) => string | undefined;
|
|
6
|
+
/** The variable symbol an identifier reads, including a shorthand property's value. */
|
|
7
|
+
export declare function identifierSymbol(identifier: ts.Identifier, checker: ts.TypeChecker): ts.Symbol | undefined;
|
|
8
|
+
/** The const initializer an identifier names, unless a property of it is written before the use. */
|
|
9
|
+
export declare function stableConstInitializer(identifier: ts.Identifier, checker: ts.TypeChecker): ts.Expression | undefined;
|
|
10
|
+
/** Resolves string literals, templates, const identifiers and bound parameters, else `undefined`. */
|
|
11
|
+
export declare function resolveKey(expression: ts.Expression | undefined, checker: ts.TypeChecker, bindings: KeyBindings, seen?: Set<ts.Node>): string | undefined;
|
|
12
|
+
/** Call-site bindings of parameters; an `expression` is the argument the call passes. */
|
|
13
|
+
export type ParamBindings = ReadonlyMap<ts.Symbol, {
|
|
14
|
+
expression?: ts.Expression | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
/** The object literal an expression denotes, through const identifiers and bound parameters. */
|
|
17
|
+
export declare function resolveObject(expression: ts.Expression | undefined, checker: ts.TypeChecker, params?: ParamBindings, seen?: Set<ts.Node>): ts.ObjectLiteralExpression | undefined;
|
|
18
|
+
export declare const ABSENT: unique symbol;
|
|
19
|
+
/** `undefined` means the property cannot be statically resolved. */
|
|
20
|
+
type Found = ts.Expression | undefined | typeof ABSENT;
|
|
21
|
+
/** The value expression a property ends with, applying properties and spreads in order. */
|
|
22
|
+
export declare function findProperty(object: ts.ObjectLiteralExpression, name: string, checker: ts.TypeChecker, params: ParamBindings, seen: Set<ts.Node>): Found;
|
|
23
|
+
/** Resolves a string property of an options object, including from spread const objects. */
|
|
24
|
+
export declare function resolveOptionString(expression: ts.Expression | undefined, name: string, checker: ts.TypeChecker, bindings: KeyBindings, params?: ParamBindings): string | undefined;
|
|
25
|
+
export {};
|