carrick 0.3.53
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/LICENSE.md +99 -0
- package/README.md +79 -0
- package/bin/carrick.mjs +168 -0
- package/dist/channel.d.ts +16 -0
- package/dist/channel.js +37 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.d.ts +154 -0
- package/dist/contract.js +141 -0
- package/dist/contract.js.map +1 -0
- package/dist/diagnostics.d.ts +54 -0
- package/dist/diagnostics.js +199 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/hook/post-edit.d.ts +13 -0
- package/dist/hook/post-edit.js +76 -0
- package/dist/hook/post-edit.js.map +1 -0
- package/dist/hook/session-start.d.ts +2 -0
- package/dist/hook/session-start.js +44 -0
- package/dist/hook/session-start.js.map +1 -0
- package/dist/init/identity.d.ts +20 -0
- package/dist/init/identity.js +60 -0
- package/dist/init/identity.js.map +1 -0
- package/dist/init/repos.d.ts +19 -0
- package/dist/init/repos.js +46 -0
- package/dist/init/repos.js.map +1 -0
- package/dist/init/run.d.ts +11 -0
- package/dist/init/run.js +228 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/settings.d.ts +50 -0
- package/dist/init/settings.js +138 -0
- package/dist/init/settings.js.map +1 -0
- package/dist/log.d.ts +2 -0
- package/dist/log.js +35 -0
- package/dist/log.js.map +1 -0
- package/dist/native.d.ts +48 -0
- package/dist/native.js +127 -0
- package/dist/native.js.map +1 -0
- package/dist/render.d.ts +65 -0
- package/dist/render.js +334 -0
- package/dist/render.js.map +1 -0
- package/dist/root.d.ts +30 -0
- package/dist/root.js +82 -0
- package/dist/root.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +255 -0
- package/dist/server.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -0
- package/package.json +72 -0
- package/plugin/.claude-plugin/plugin.json +6 -0
- package/plugin/.lsp.json +14 -0
- package/plugin/hooks/hooks.json +27 -0
- package/sidecar/dist/src/bundler.d.ts +141 -0
- package/sidecar/dist/src/bundler.js +680 -0
- package/sidecar/dist/src/capture/anchors.d.ts +61 -0
- package/sidecar/dist/src/capture/anchors.js +1132 -0
- package/sidecar/dist/src/capture/api.d.ts +378 -0
- package/sidecar/dist/src/capture/api.js +10 -0
- package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
- package/sidecar/dist/src/capture/augmentations.js +60 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
- package/sidecar/dist/src/capture/check-classify.js +189 -0
- package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
- package/sidecar/dist/src/capture/check-deep.js +91 -0
- package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
- package/sidecar/dist/src/capture/check-poison.js +155 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
- package/sidecar/dist/src/capture/check-probe.js +147 -0
- package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
- package/sidecar/dist/src/capture/check-scrub.js +71 -0
- package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
- package/sidecar/dist/src/capture/check-workspace.js +218 -0
- package/sidecar/dist/src/capture/check.d.ts +39 -0
- package/sidecar/dist/src/capture/check.js +450 -0
- package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
- package/sidecar/dist/src/capture/deep-walk.js +243 -0
- package/sidecar/dist/src/capture/index.d.ts +36 -0
- package/sidecar/dist/src/capture/index.js +477 -0
- package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
- package/sidecar/dist/src/capture/lockfile.js +490 -0
- package/sidecar/dist/src/capture/machinery.d.ts +59 -0
- package/sidecar/dist/src/capture/machinery.js +160 -0
- package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
- package/sidecar/dist/src/capture/node-builder.js +123 -0
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
- package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
- package/sidecar/dist/src/capture/self-check.d.ts +38 -0
- package/sidecar/dist/src/capture/self-check.js +317 -0
- package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
- package/sidecar/dist/src/capture/specifiers.js +56 -0
- package/sidecar/dist/src/definition-resolver.d.ts +57 -0
- package/sidecar/dist/src/definition-resolver.js +153 -0
- package/sidecar/dist/src/index.d.ts +14 -0
- package/sidecar/dist/src/index.js +564 -0
- package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
- package/sidecar/dist/src/monorepo-builder.js +584 -0
- package/sidecar/dist/src/project-loader.d.ts +130 -0
- package/sidecar/dist/src/project-loader.js +399 -0
- package/sidecar/dist/src/type-inferrer.d.ts +940 -0
- package/sidecar/dist/src/type-inferrer.js +3540 -0
- package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
- package/sidecar/dist/src/type-structural-expander.js +283 -0
- package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
- package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
- package/sidecar/dist/src/types.d.ts +669 -0
- package/sidecar/dist/src/types.js +5 -0
- package/sidecar/dist/src/validators.d.ts +2214 -0
- package/sidecar/dist/src/validators.js +336 -0
- package/sidecar/package.json +6 -0
- package/templates/carrick.json +7 -0
- package/templates/carrick.yml +45 -0
|
@@ -0,0 +1,940 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Inferrer - Scope-based type inference with payload unwrapping
|
|
3
|
+
*
|
|
4
|
+
* This module extracts types even when developers don't write explicit
|
|
5
|
+
* annotations. It uses span-based node lookup (no line windows) to target
|
|
6
|
+
* precise expressions provided by the Rust/LLM pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Key feature: Agent-informed payload unwrapping
|
|
9
|
+
* - Extracts payload types from machinery wrappers (Response<T>, AxiosResponse<T>, etc.)
|
|
10
|
+
* - Supports union/intersection composition
|
|
11
|
+
* - Recursive unwrapping with depth limits
|
|
12
|
+
*
|
|
13
|
+
* Framework agnosticism: The LLM emits the payload subexpression directly (e.g.,
|
|
14
|
+
* the `users` in `res.json(users)`, `ctx.body = users`, `h.response(users)`, or
|
|
15
|
+
* `return users`). The sidecar resolves that node and reads its type — no
|
|
16
|
+
* framework-specific method-name lists live here. For payload-less handlers
|
|
17
|
+
* (redirects, 204s), the LLM emits null and we fall back to the containing
|
|
18
|
+
* function's return type.
|
|
19
|
+
*/
|
|
20
|
+
import { Project } from 'ts-morph';
|
|
21
|
+
import type { InferRequestItem, InferResult, ExtractionConfig } from './types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Strongly-discriminating member names of HTTP transport machinery — the
|
|
24
|
+
* fetch/DOM `Response` & `Request`, a Node `http.ServerResponse`, a framework
|
|
25
|
+
* reply object. A type declared in a lib or `node_modules` origin that carries
|
|
26
|
+
* a subset of these is framework machinery, never a user contract: the
|
|
27
|
+
* PRODUCER-side structural mirror of the consumer `machineryIndicators`
|
|
28
|
+
* (ExtractionConfig), used to reject a wrapper envelope whose response field is
|
|
29
|
+
* a raw `Response` (carrick#371) instead of emitting it as a comparable type.
|
|
30
|
+
*
|
|
31
|
+
* Framework-agnostic by construction: no framework NAME appears here, only the
|
|
32
|
+
* shared HTTP-message surface. The names are deliberately the ones no
|
|
33
|
+
* JSON payload ever carries (`ok`, `redirected`, `bodyUsed`, `arrayBuffer`,
|
|
34
|
+
* `writeHead`, ...), so the origin gate + `MACHINERY_INDICATOR_THRESHOLD` never
|
|
35
|
+
* fire on real data. Kept in lockstep with the capture-seam mirror
|
|
36
|
+
* `capture/machinery.ts` (the seam forbids sharing a module across it, same as
|
|
37
|
+
* `BUILTIN_ANCHOR_SYMBOLS` mirrors `socket_io.rs`). Exported so a drift-guard
|
|
38
|
+
* test (`machinery-indicator-mirror.test.ts`) asserts the two sets stay equal.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MACHINERY_MEMBER_INDICATORS: Set<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Options for TypeInferrer construction
|
|
43
|
+
*/
|
|
44
|
+
export interface TypeInferrerOptions {
|
|
45
|
+
/** The ts-morph Project instance */
|
|
46
|
+
project: Project;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* TypeInferrer - Extracts types from source code, both explicit and inferred
|
|
50
|
+
*
|
|
51
|
+
* Usage:
|
|
52
|
+
* const inferrer = new TypeInferrer({ project });
|
|
53
|
+
* const result = inferrer.infer(requests, extractionConfig);
|
|
54
|
+
*/
|
|
55
|
+
export declare class TypeInferrer {
|
|
56
|
+
private readonly project;
|
|
57
|
+
constructor(options: TypeInferrerOptions);
|
|
58
|
+
/**
|
|
59
|
+
* Infer types for the given requests
|
|
60
|
+
*
|
|
61
|
+
* @param requests - Array of inference requests
|
|
62
|
+
* @param extractionConfig - Agent-generated extraction config for payload unwrapping
|
|
63
|
+
* @returns InferResult with inferred types or errors
|
|
64
|
+
*/
|
|
65
|
+
infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig): InferResult;
|
|
66
|
+
/**
|
|
67
|
+
* Infer a single type from a request
|
|
68
|
+
*/
|
|
69
|
+
private inferSingle;
|
|
70
|
+
/**
|
|
71
|
+
* Get or add a source file to the project
|
|
72
|
+
*/
|
|
73
|
+
private getSourceFile;
|
|
74
|
+
private inferFunctionReturn;
|
|
75
|
+
/**
|
|
76
|
+
* Build a `function_return` inferred type from an already-resolved function
|
|
77
|
+
* node: unwrap `Promise<…>` / async-iterator transport, apply the extraction
|
|
78
|
+
* config to the awaited type, and expand a bare named return structurally.
|
|
79
|
+
* Extracted from `inferFunctionReturn` so the route-registration path
|
|
80
|
+
* (`inferResponseBody` following a handler) can reuse the exact same logic
|
|
81
|
+
* against the handler function instead of the containing function.
|
|
82
|
+
*/
|
|
83
|
+
private buildFunctionReturnInferredType;
|
|
84
|
+
/**
|
|
85
|
+
* Infer a function's return type for the signature hint. Unlike
|
|
86
|
+
* `inferFunctionReturn`, this does NOT unwrap Promise or apply wrapper
|
|
87
|
+
* rules — a function that returns `Promise<AuthResult>` should show exactly
|
|
88
|
+
* that in its signature. Used by the function-signature collection pass.
|
|
89
|
+
*/
|
|
90
|
+
private inferSignatureReturn;
|
|
91
|
+
/**
|
|
92
|
+
* Infer the type of a single named parameter. `is_explicit` reflects whether
|
|
93
|
+
* the parameter carries a source annotation; the type string is the
|
|
94
|
+
* compiler's view either way (so contextually-typed callback params resolve
|
|
95
|
+
* even without an annotation). Uses ts-morph's default `getText()` form,
|
|
96
|
+
* which keeps named types as names and bounds depth via the compiler's own
|
|
97
|
+
* truncation.
|
|
98
|
+
*/
|
|
99
|
+
private inferFunctionParam;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a `function_param` locator against a function's parameter list.
|
|
102
|
+
* Three shapes, tried in order:
|
|
103
|
+
*
|
|
104
|
+
* 1. A parameter whose name matches exactly (`(payload) => …` ← "payload").
|
|
105
|
+
* 2. A parameter whose DESTRUCTURED BINDING PATTERN matches the locator
|
|
106
|
+
* text under whitespace normalization (`({ time, run }) => …` ←
|
|
107
|
+
* "{ time, run }") — the handler destructures the payload itself, so the
|
|
108
|
+
* pattern's own type IS the payload type.
|
|
109
|
+
* 3. A named BINDING ELEMENT inside a destructured parameter
|
|
110
|
+
* (`({ payload }) => …` ← "payload") — the payload is one property of an
|
|
111
|
+
* envelope param, and the checker projects the element's type
|
|
112
|
+
* (catalog-worker handlers: `params: { id, payload: Infer<…> }`).
|
|
113
|
+
*
|
|
114
|
+
* All three read the type off a node the checker has already instantiated,
|
|
115
|
+
* so generic wrappers (topic-map emitters, schema catalogs, channel handles)
|
|
116
|
+
* resolve without any named payload symbol existing anywhere.
|
|
117
|
+
*/
|
|
118
|
+
private resolveParamTarget;
|
|
119
|
+
private inferResponseBody;
|
|
120
|
+
private inferCallResult;
|
|
121
|
+
private inferVariable;
|
|
122
|
+
private inferExpression;
|
|
123
|
+
private inferRequestBody;
|
|
124
|
+
private resolveCallResultTerminalNode;
|
|
125
|
+
private extractBindingFromCall;
|
|
126
|
+
private extractBindingNames;
|
|
127
|
+
private getPrimaryBindingNode;
|
|
128
|
+
private collectDefUseNodes;
|
|
129
|
+
private expressionUsesNames;
|
|
130
|
+
private isIdentifierUsage;
|
|
131
|
+
private isInFunctionScope;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the awaited type: `Promise<X>` / `PromiseLike<X>` / `Awaited<X>` →
|
|
134
|
+
* `X` at the type level (recursively, so `Awaited<Promise<X>>` → `X`); any
|
|
135
|
+
* non-thenable type passes through UNCHANGED.
|
|
136
|
+
*
|
|
137
|
+
* ts-morph 25.0.1 does not expose the compiler's `getAwaitedType` on its
|
|
138
|
+
* `Type` wrapper (only `AwaitableNode.isAwaited()` on AST nodes, which is
|
|
139
|
+
* unrelated), so we resolve structurally on the symbol/alias name instead of
|
|
140
|
+
* gating on the literal `'Promise'` symbol. This generalizes past `Promise<T>`
|
|
141
|
+
* to `PromiseLike<T>` and the `Awaited<T>` utility type without over-unwrapping:
|
|
142
|
+
* a non-thenable like `AsyncGenerator<T>` is NOT awaitable and is returned
|
|
143
|
+
* unchanged, so the `unwrapAsyncIterableType` step that runs right after still
|
|
144
|
+
* sees (and peels) the iterator wrapper as before.
|
|
145
|
+
*/
|
|
146
|
+
private unwrapPromiseType;
|
|
147
|
+
/**
|
|
148
|
+
* `AsyncGenerator<T, …>` / `AsyncIterableIterator<T>` / `AsyncIterator<T>` /
|
|
149
|
+
* `Generator<T, …>` / `IterableIterator<T>` → `T` (the yield type) at the type
|
|
150
|
+
* level; any other type passes through. A GraphQL subscription resolver written
|
|
151
|
+
* as `async function* x(): AsyncGenerator<Order>` carries its contract in the
|
|
152
|
+
* yield position, so the iterator wrapper must be peeled the same way Promise is
|
|
153
|
+
* before structural expansion — otherwise the response contract resolves to the
|
|
154
|
+
* library `AsyncGenerator<…>` machinery instead of the bare `Order`.
|
|
155
|
+
*/
|
|
156
|
+
private unwrapAsyncIterableType;
|
|
157
|
+
/** The "leave the type as it is" result every bail-out path shares. */
|
|
158
|
+
private noUnwrap;
|
|
159
|
+
/**
|
|
160
|
+
* Unwrap a type using the agent-generated ExtractionConfig.
|
|
161
|
+
*/
|
|
162
|
+
private unwrapTypeWithConfig;
|
|
163
|
+
/**
|
|
164
|
+
* Core unwrapping implementation with ExtractionConfig rules.
|
|
165
|
+
*
|
|
166
|
+
* Requirements:
|
|
167
|
+
* 1. Exact wrapperSymbols match extracts (gated on originModuleGlobs when
|
|
168
|
+
* the rule carries them — names like `Response` are shared by the DOM,
|
|
169
|
+
* frameworks, and HTTP clients)
|
|
170
|
+
* 2. machineryIndicators only trigger unwrap if originModuleGlobs also match
|
|
171
|
+
* 3. Handle unions and intersections
|
|
172
|
+
* 4. Support recursive unwrapping with depth limits
|
|
173
|
+
* 5. A rule that matches but extracts nothing never blocks later rules;
|
|
174
|
+
* only after every rule has run does an origin-verified match with no
|
|
175
|
+
* recoverable payload collapse to `unknown`
|
|
176
|
+
*/
|
|
177
|
+
private unwrapType;
|
|
178
|
+
/**
|
|
179
|
+
* Try to unwrap a type using a single ExtractionRule.
|
|
180
|
+
*/
|
|
181
|
+
private tryUnwrapWithRule;
|
|
182
|
+
/**
|
|
183
|
+
* Extract the payload type from a matched wrapper. Returns null when the
|
|
184
|
+
* rule matched the wrapper but no payload is recoverable from generics or
|
|
185
|
+
* property paths — the caller decides what a payload-less match means
|
|
186
|
+
* (verified machinery collapses to `unknown` after every rule has run;
|
|
187
|
+
* a name-only match leaves the type untouched).
|
|
188
|
+
*/
|
|
189
|
+
private extractPayloadFromWrapper;
|
|
190
|
+
/**
|
|
191
|
+
* True when a producer RESPONSE type IS or CONTAINS framework transport
|
|
192
|
+
* machinery (a fetch/DOM `Response`, a Node `ServerResponse`, a reply object)
|
|
193
|
+
* — the artifact behind carrick#371, where a wrapped handler's literal return
|
|
194
|
+
* envelope `{ response: Response; error?: undefined } | { ...; error: Error }`
|
|
195
|
+
* was captured as the response contract. Machinery is never a comparable
|
|
196
|
+
* contract, so a response path that resolves here abstains (honest `unknown`)
|
|
197
|
+
* rather than emit a concrete-but-false type.
|
|
198
|
+
*
|
|
199
|
+
* DETECTS, exactly (see `typeIsOrContainsMachinery`):
|
|
200
|
+
* 1. the type itself is machinery (`typeIsFrameworkMachinery`);
|
|
201
|
+
* 2. a union/intersection member is machinery (the envelope union);
|
|
202
|
+
* 3. a DIRECT property's type is machinery, ONE level of descent only
|
|
203
|
+
* (`{ response: Response; error }`).
|
|
204
|
+
*
|
|
205
|
+
* DELIBERATELY NOT DETECTED — listed so this comment never overstates the
|
|
206
|
+
* guarantee (an overstated safety comment is what bit sibling PR #442). Each is
|
|
207
|
+
* a non-regression: it leaves the pre-existing verdict unchanged and never
|
|
208
|
+
* manufactures a new wrong one, tracked as a follow-up:
|
|
209
|
+
* - machinery nested deeper than one property level (a property whose type
|
|
210
|
+
* is itself a nested object wrapping the machinery);
|
|
211
|
+
* - a property typed `Promise<Response>` (the property type is NOT awaited /
|
|
212
|
+
* Promise-unwrapped before the machinery check);
|
|
213
|
+
* - an array element type: `Response[]` is not descended to its element;
|
|
214
|
+
* - `interface X extends Response` declared in USER source — the origin gate
|
|
215
|
+
* is lib/`node_modules` only, so a user-declared subtype reads as a real
|
|
216
|
+
* contract, not machinery;
|
|
217
|
+
* - a function / call-signature return type: the response paths that call
|
|
218
|
+
* this resolve a handler's RETURN (an envelope/object), never a function
|
|
219
|
+
* value, so no call-signature descent happens here. The capture-seam
|
|
220
|
+
* mirror `capture/machinery.ts` DOES descend call signatures — that is
|
|
221
|
+
* where the wrapper-FUNCTION type the Infer fallback resolves is caught.
|
|
222
|
+
*
|
|
223
|
+
* The origin gate in `typeIsFrameworkMachinery` keeps a user object whose
|
|
224
|
+
* fields merely share a member name from tripping.
|
|
225
|
+
*/
|
|
226
|
+
private typeIsOrContainsResponseMachinery;
|
|
227
|
+
private typeIsOrContainsMachinery;
|
|
228
|
+
/**
|
|
229
|
+
* True when `type` itself is an HTTP-machinery type: it structurally carries
|
|
230
|
+
* at least `MACHINERY_INDICATOR_THRESHOLD` of the strongly-discriminating
|
|
231
|
+
* `MACHINERY_MEMBER_INDICATORS`, AND its symbol is declared in a lib
|
|
232
|
+
* (`lib.dom.d.ts`, ...) or `node_modules` origin. Both gates are required —
|
|
233
|
+
* the indicator subset alone essentially never matches a JSON payload, and
|
|
234
|
+
* the origin gate makes certain a user's own local type sharing those member
|
|
235
|
+
* names is never mistaken for framework machinery (the advisor's guard).
|
|
236
|
+
*/
|
|
237
|
+
private typeIsFrameworkMachinery;
|
|
238
|
+
/**
|
|
239
|
+
* True once the type carries at least `MACHINERY_INDICATOR_THRESHOLD` DISTINCT
|
|
240
|
+
* `MACHINERY_MEMBER_INDICATORS` (own + apparent). Deduplicates by name (own and
|
|
241
|
+
* apparent property lists overlap) and early-returns the moment the threshold
|
|
242
|
+
* is reached — the callers only need the boolean, never the full count.
|
|
243
|
+
*/
|
|
244
|
+
private hasMachineryIndicatorThreshold;
|
|
245
|
+
/**
|
|
246
|
+
* True when the symbol is declared in a TypeScript lib file (`lib.*.d.ts`) or
|
|
247
|
+
* under `node_modules` — i.e. framework/runtime machinery, not user source.
|
|
248
|
+
* Works on a bare checkout: the DOM `Response`/`Request` resolve from the
|
|
249
|
+
* bundled `lib.dom.d.ts` even with no installed dependencies.
|
|
250
|
+
*/
|
|
251
|
+
private symbolIsLibOrExternalOrigin;
|
|
252
|
+
/**
|
|
253
|
+
* Check if a type has machinery indicator properties/methods.
|
|
254
|
+
*/
|
|
255
|
+
private typeHasMachineryIndicators;
|
|
256
|
+
/**
|
|
257
|
+
* Check if a symbol's declarations originate from modules matching the globs.
|
|
258
|
+
*/
|
|
259
|
+
private symbolOriginatesFromModules;
|
|
260
|
+
/**
|
|
261
|
+
* Simple glob matching for module paths.
|
|
262
|
+
* Supports: exact match, a trailing "*" wildcard, and "package/*" patterns.
|
|
263
|
+
*
|
|
264
|
+
* Matches are segment-bounded: the glob names a package (or package
|
|
265
|
+
* subpath) under node_modules, and the match must end at a path-segment
|
|
266
|
+
* boundary — `got` matches `node_modules/got/...` but never
|
|
267
|
+
* `node_modules/got-scraping/...`. This matters because the exact-symbol
|
|
268
|
+
* origin gate routes shared names like `Response` through here.
|
|
269
|
+
*/
|
|
270
|
+
private filePathMatchesModuleGlob;
|
|
271
|
+
/**
|
|
272
|
+
* Check if a type string is "useless" for payload purposes.
|
|
273
|
+
*/
|
|
274
|
+
private isUselessType;
|
|
275
|
+
private unwrapExpressionNode;
|
|
276
|
+
/**
|
|
277
|
+
* If `node` is a `JSON.stringify(arg)` call, return the (expression-unwrapped)
|
|
278
|
+
* first argument so its type is read instead of the call's `string` result.
|
|
279
|
+
* Otherwise return `node` unchanged. Any non-`JSON.stringify` call, or a
|
|
280
|
+
* `JSON.stringify()` with no argument, is left alone.
|
|
281
|
+
*/
|
|
282
|
+
private unwrapJsonStringifyArg;
|
|
283
|
+
private extractExplicitTypeFromAncestor;
|
|
284
|
+
/**
|
|
285
|
+
* Render an explicit annotation (`as T`, `<T>`, or a typed binding) as
|
|
286
|
+
* fully-structural text.
|
|
287
|
+
*
|
|
288
|
+
* `typeNode.getText()` keeps a named type as its bare identifier
|
|
289
|
+
* (`OrderView`, `Promise<Payment>`). A bare name is fine inside the source
|
|
290
|
+
* project but becomes a dangling reference in the cross-repo `.d.ts` bundle,
|
|
291
|
+
* which carries only alias lines and no source declarations — it resolves to
|
|
292
|
+
* `any` and the comparison reads `unverifiable`. Resolving the annotation to
|
|
293
|
+
* its `Type`, stripping `Promise<…>` at the type level, and expanding the
|
|
294
|
+
* object structurally (shared with `definition-resolver.ts`) lands the real
|
|
295
|
+
* shape (`{ id: string; currency: string }`) in the bundle so the consumer
|
|
296
|
+
* can actually be compared.
|
|
297
|
+
*
|
|
298
|
+
* Falls back to the bare annotation text when the resolved type can't be
|
|
299
|
+
* expanded to a structural form (primitives, library types, unresolvable
|
|
300
|
+
* references), so a non-object annotation behaves exactly as before.
|
|
301
|
+
*/
|
|
302
|
+
private expandAnnotationTypeNode;
|
|
303
|
+
/**
|
|
304
|
+
* Producer-side analogue of `expandAnnotationTypeNode` that works from a
|
|
305
|
+
* resolved `Type` rather than a syntactic annotation node.
|
|
306
|
+
*
|
|
307
|
+
* `inferResponseBody`/`inferFunctionReturn` resolve a payload to a named
|
|
308
|
+
* object type (e.g. `Payment`), then render it with `typeText`, which keeps
|
|
309
|
+
* the bare name. In the source-less cross-repo `.d.ts` bundle that name is a
|
|
310
|
+
* dangling `export type <alias> = Payment;` → resolves to `any` →
|
|
311
|
+
* `unverifiable` → `compat = None`. Expanding the resolved object structurally
|
|
312
|
+
* lands the real members (`{ id: string; … }`) in the bundle so the producer
|
|
313
|
+
* can be compared. Mirror of #257's consumer-side fix; keeps the bare text for
|
|
314
|
+
* primitives, library types, and anything the expander leaves by name.
|
|
315
|
+
*
|
|
316
|
+
* `fallback` is the already-computed type text (post Promise/wrapper unwrap),
|
|
317
|
+
* preserved verbatim when expansion does not inline an object.
|
|
318
|
+
*/
|
|
319
|
+
private expandResolvedTypeStructural;
|
|
320
|
+
/**
|
|
321
|
+
* Recover a response contract from a handler's own `return` statements when
|
|
322
|
+
* the resolved return type carries none.
|
|
323
|
+
*
|
|
324
|
+
* A file-based route `loader`/`action` almost never annotates its return: the
|
|
325
|
+
* payload goes through a helper — `json(payload)`, `reply(payload, opts)`,
|
|
326
|
+
* `wrap(request, json(payload))` — and the helper's result is transport, not
|
|
327
|
+
* contract. Two ways that collapses today:
|
|
328
|
+
*
|
|
329
|
+
* * the helper is typed and returns machinery (`Promise<Response>`), so the
|
|
330
|
+
* carrick#371 guard abstains and the manifest reads `unknown`;
|
|
331
|
+
* * the helper comes from a package with no installed declaration (the
|
|
332
|
+
* scanner reads a bare checkout), so it resolves to `any` and the whole
|
|
333
|
+
* response reads `any`.
|
|
334
|
+
*
|
|
335
|
+
* In both, the contract is the helper's ARGUMENT — every framework that ships
|
|
336
|
+
* such a helper is generic over it. This walks the handler's returned
|
|
337
|
+
* expressions, descends through wrapper calls to the first argument that
|
|
338
|
+
* carries a payload, and unions the surviving branches. It is structural
|
|
339
|
+
* throughout: no helper, framework or package name is matched anywhere, and
|
|
340
|
+
* the recovery only runs where the existing path already had nothing.
|
|
341
|
+
*
|
|
342
|
+
* `statedOnly` is the guard for the unresolvable-callee case. A return type
|
|
343
|
+
* of `any` does not say the callee was a response helper, only that it could
|
|
344
|
+
* not be resolved, and on a bare checkout that is true of most imported
|
|
345
|
+
* callees: `return db.findMany({ where })` would otherwise report the query
|
|
346
|
+
* object as the endpoint's contract, which is a false contract and worse
|
|
347
|
+
* than `unknown`. With `statedOnly` set, only an argument the source
|
|
348
|
+
* annotates (`satisfies` / `as` / `<T>`) counts, because there the developer
|
|
349
|
+
* stated the contract. The machinery case leaves it clear: the callee is
|
|
350
|
+
* known transport, so its argument is the payload whatever its type.
|
|
351
|
+
*
|
|
352
|
+
* Returns `null` (a logged limitation, never a guess) when no returned
|
|
353
|
+
* expression yields an argument type this may read.
|
|
354
|
+
*/
|
|
355
|
+
private recoverPayloadFromReturnStatements;
|
|
356
|
+
/**
|
|
357
|
+
* The expressions a function returns, excluding returns belonging to nested
|
|
358
|
+
* functions (a `.map(x => …)` callback returns a row, not the response).
|
|
359
|
+
* A concise arrow body is itself the returned expression.
|
|
360
|
+
*/
|
|
361
|
+
private returnedExpressions;
|
|
362
|
+
/**
|
|
363
|
+
* The expressions that can carry this handler's response: its own returned
|
|
364
|
+
* expressions, plus the returned expressions of any function the handler
|
|
365
|
+
* hands DIRECTLY to a returned call (carrick#707).
|
|
366
|
+
*
|
|
367
|
+
* `return settle(...).then(ok => serialise(body), err => serialise(problem))`
|
|
368
|
+
* returns a call whose result is whatever the callbacks produce, so the
|
|
369
|
+
* callbacks' returns are this handler's returns as surely as its own. That
|
|
370
|
+
* shape is how a result-type API, a promise combinator or a matcher is
|
|
371
|
+
* written, and on a bare checkout the callee resolves to nothing, so the
|
|
372
|
+
* outer call's type says nothing at all.
|
|
373
|
+
*
|
|
374
|
+
* Only ONE level, and only from the returned call's own arguments. A
|
|
375
|
+
* callback nested deeper is inside an expression the handler computes, not
|
|
376
|
+
* an expression it returns — `return rows.map(r => …)` returns a row list,
|
|
377
|
+
* and reading the callback there would report a row as the response. Each
|
|
378
|
+
* flattened expression still has to pass the payload rule below, which no
|
|
379
|
+
* bare object literal does.
|
|
380
|
+
*/
|
|
381
|
+
private responseReturnedExpressions;
|
|
382
|
+
/**
|
|
383
|
+
* The callees these returned expressions PROVE are response serialisers.
|
|
384
|
+
*
|
|
385
|
+
* A return type of `any` says the callee could not be resolved, not that it
|
|
386
|
+
* was a serialiser — the reason the unresolvable-callee case is otherwise
|
|
387
|
+
* restricted to arguments the source annotates. But a handler that writes
|
|
388
|
+
*
|
|
389
|
+
* return f(problem, { status: 401 });
|
|
390
|
+
* return f(payload);
|
|
391
|
+
*
|
|
392
|
+
* has stated what `f` is in its own source: a call that takes a body and,
|
|
393
|
+
* beside it, an HTTP status is a response serialiser, and nothing else is
|
|
394
|
+
* written that way. That is evidence the compiler does not need to resolve
|
|
395
|
+
* anything to see, so it survives the bare checkout CI scans, and it makes
|
|
396
|
+
* the second call's unannotated argument a payload rather than a guess.
|
|
397
|
+
*
|
|
398
|
+
* Three deliberate limits, each a negative test:
|
|
399
|
+
* - only RETURNED expressions count. A handler may well call a logger with
|
|
400
|
+
* `{ status: 500 }`; what it returns is what it serialises.
|
|
401
|
+
* - the status-stating argument must sit at index >= 1. A status in FIRST
|
|
402
|
+
* position is a field of the body (`{ status: 503, note }`), and reading
|
|
403
|
+
* it as evidence would promote every callee that takes a status field.
|
|
404
|
+
* - the evidence is per HANDLER, never cached across files. It says what
|
|
405
|
+
* this handler does, and that is all it is used for.
|
|
406
|
+
*
|
|
407
|
+
* Callees are keyed by resolved symbol where one exists — an import alias
|
|
408
|
+
* resolves locally even when its module does not — and by callee text
|
|
409
|
+
* otherwise.
|
|
410
|
+
*/
|
|
411
|
+
private calleesProvenSerialiser;
|
|
412
|
+
/**
|
|
413
|
+
* Stable identity for a call's callee within one file: its resolved symbol
|
|
414
|
+
* when the compiler has one, else the callee's source text.
|
|
415
|
+
*/
|
|
416
|
+
private calleeIdentity;
|
|
417
|
+
/**
|
|
418
|
+
* The payload argument of a returned response-helper call, or `undefined`.
|
|
419
|
+
*
|
|
420
|
+
* Walks the call's arguments in source order: the first whose type reads as a
|
|
421
|
+
* contract wins; an argument that is itself a call is descended into, which is
|
|
422
|
+
* what recovers `wrap(request, json(payload))` — the transport wrapper's own
|
|
423
|
+
* first argument is the request, which never reads as a contract.
|
|
424
|
+
*
|
|
425
|
+
* A call whose sibling options object states a >= 400 status is an error
|
|
426
|
+
* branch and contributes nothing: the response contract of an endpoint is the
|
|
427
|
+
* shape it returns when it succeeds.
|
|
428
|
+
*
|
|
429
|
+
* `statedOnly` narrows what counts as a payload to an argument the source
|
|
430
|
+
* annotates; see `recoverPayloadFromReturnStatements`. It is lifted for a
|
|
431
|
+
* callee the handler's own returned calls PROVE is a serialiser
|
|
432
|
+
* (`calleesProvenSerialiser`): there the source has stated what the callee
|
|
433
|
+
* is, so its argument is the payload even unannotated.
|
|
434
|
+
*/
|
|
435
|
+
private responseHelperPayloadNode;
|
|
436
|
+
/**
|
|
437
|
+
* True when a node's type reads as a response payload.
|
|
438
|
+
*
|
|
439
|
+
* Object-shaped only (an object literal, a named interface, an array of
|
|
440
|
+
* either, or a union containing one). A bare primitive argument is a
|
|
441
|
+
* redirect location, a status code or a body string — `redirectTo("/next")`
|
|
442
|
+
* must never report `"/next"` as the endpoint's contract. A stated
|
|
443
|
+
* `satisfies`/`as` annotation counts even when its declaration is missing:
|
|
444
|
+
* the source says what the contract is.
|
|
445
|
+
*
|
|
446
|
+
* Under `statedOnly` the annotation is the ONLY thing that counts, so an
|
|
447
|
+
* unresolvable callee's arguments never become a contract by accident.
|
|
448
|
+
*/
|
|
449
|
+
private nodeCarriesPayloadContract;
|
|
450
|
+
/** Object, array-of-object, or a union/intersection containing one. */
|
|
451
|
+
private typeIsObjectShaped;
|
|
452
|
+
/**
|
|
453
|
+
* The `satisfies X` / `as X` / `<X>` annotation node on an expression, when
|
|
454
|
+
* the source states its type. `satisfies` is the shape a route uses to claim
|
|
455
|
+
* a shared response contract without widening the literal.
|
|
456
|
+
*/
|
|
457
|
+
private statedTypeNodeOf;
|
|
458
|
+
/**
|
|
459
|
+
* True when an argument states a >= 400 status: an options object carrying
|
|
460
|
+
* `status`/`statusCode`, or a bare status code (`send(body, 404)`).
|
|
461
|
+
*
|
|
462
|
+
* Read from the AST first: `{ status: 400 }` in an argument position widens
|
|
463
|
+
* to `{ status: number }`, so the literal only survives syntactically. The
|
|
464
|
+
* type check behind it catches `as const` and hoisted option objects.
|
|
465
|
+
*/
|
|
466
|
+
private statesErrorStatus;
|
|
467
|
+
/**
|
|
468
|
+
* The HTTP status an argument states, or `undefined`.
|
|
469
|
+
*
|
|
470
|
+
* Read from the AST first: `{ status: 400 }` in an argument position widens
|
|
471
|
+
* to `{ status: number }`, so the literal only survives syntactically. The
|
|
472
|
+
* type check behind it catches `as const` and hoisted option objects. Only
|
|
473
|
+
* values in the HTTP range count — an arbitrary number named `status` on a
|
|
474
|
+
* domain object (`{ status: 2 }`) states nothing about transport.
|
|
475
|
+
*/
|
|
476
|
+
private statedStatusCode;
|
|
477
|
+
/**
|
|
478
|
+
* Peel the wrappers that do not change an expression's payload: parentheses
|
|
479
|
+
* and `await`. Unlike `unwrapExpressionNode` this KEEPS `as`/`satisfies`,
|
|
480
|
+
* because the annotation is exactly what the recovery wants to read.
|
|
481
|
+
*/
|
|
482
|
+
private peelTransparentExpression;
|
|
483
|
+
/**
|
|
484
|
+
* The deterministic source symbol of a resolved type (`Payment` for a payload
|
|
485
|
+
* typed `Payment`), or `undefined` when there is no single user-defined
|
|
486
|
+
* symbol to anchor on. This is the same `getSymbol() || getAliasSymbol()` name
|
|
487
|
+
* the socket anchor already derives (`socket_io.rs`), filtered through
|
|
488
|
+
* `BUILTIN_ANCHOR_SYMBOLS` so TS/lib globals (`Promise`, `Array`, `Date`,
|
|
489
|
+
* primitives, …) never become an anchor. Used to populate
|
|
490
|
+
* `primary_type_symbol` so the manifest anchor no longer depends on the LLM.
|
|
491
|
+
*/
|
|
492
|
+
private primaryTypeSymbol;
|
|
493
|
+
/**
|
|
494
|
+
* Declaration file (absolute path) of the anchor symbol
|
|
495
|
+
* `primaryTypeSymbol` reports for this type, or `undefined` when the type
|
|
496
|
+
* has no user-facing anchor or no source declaration. The scanner's
|
|
497
|
+
* pub/sub two-anchor arbitration (carrick#413) uses this to re-aim a
|
|
498
|
+
* demoted explicit bundle request: the bundler resolves a `SymbolRequest`
|
|
499
|
+
* only against declarations IN its `source_file`, so the request must
|
|
500
|
+
* point at the file that actually declares the tsc-witnessed payload type.
|
|
501
|
+
*
|
|
502
|
+
* Only declaration kinds the bundler's `validateSymbols` can resolve
|
|
503
|
+
* (interface, type alias, class, enum, function, variable) count. A
|
|
504
|
+
* symbol's declaration list can also contain re-export machinery — a
|
|
505
|
+
* barrel's `ExportSpecifier` (`export { Foo } from './foo'`) points at a
|
|
506
|
+
* file that does not DECLARE the type, and a request re-aimed there would
|
|
507
|
+
* fail validation, turning a resolvable explicit type into `unknown`.
|
|
508
|
+
* With no declaring node, no source is reported and the arbitration
|
|
509
|
+
* fails closed to the explicit anchor.
|
|
510
|
+
*/
|
|
511
|
+
private primaryTypeSymbolSource;
|
|
512
|
+
/**
|
|
513
|
+
* The anchor a `satisfies X` / `as X` / `<X>` annotation states IN SOURCE:
|
|
514
|
+
* the name written at the annotation, where its type is declared, and the
|
|
515
|
+
* array levels wrapped around it (`satisfies Order[]` → `Order`, depth 1).
|
|
516
|
+
*
|
|
517
|
+
* carrick#768: reading the anchor off the RESOLVED type works for an
|
|
518
|
+
* interface, whose type carries its own symbol, and fails for
|
|
519
|
+
*
|
|
520
|
+
* export type OrderBody = Inferred<typeof OrderSchema>;
|
|
521
|
+
*
|
|
522
|
+
* because the alias resolves to an instantiated type TypeScript keeps no
|
|
523
|
+
* alias symbol on, so `getSymbol()` answers the synthetic `__type`. The
|
|
524
|
+
* route then published a correct shape with no name, and
|
|
525
|
+
* `primary_type_symbol` is precisely how a reader gets from a route to the
|
|
526
|
+
* type its consumer imports. The annotation names it either way, so read
|
|
527
|
+
* the name from the AST and confirm it against the declaration it resolves
|
|
528
|
+
* to. This is the fallback for a resolved type that anchored nothing, never
|
|
529
|
+
* an override of one that did.
|
|
530
|
+
*
|
|
531
|
+
* Four things are rejected, so a name only anchors when it is genuinely the
|
|
532
|
+
* contract a consumer would import:
|
|
533
|
+
* - a generic instantiation (`satisfies Envelope<Order>`) names the
|
|
534
|
+
* WRAPPER, and a bare `Envelope` is not the payload;
|
|
535
|
+
* - a name resolving to no type declaration — an import of a value, a
|
|
536
|
+
* re-export the checker cannot follow — is not importable as a type;
|
|
537
|
+
* - a declaration in the TypeScript default library, and every
|
|
538
|
+
* `BUILTIN_ANCHOR_SYMBOLS` name, which describe machinery not contracts;
|
|
539
|
+
* - an inline annotation (`satisfies { id: string }`), which names nothing.
|
|
540
|
+
*/
|
|
541
|
+
private writtenAnchorOf;
|
|
542
|
+
/**
|
|
543
|
+
* Peel array levels off a resolved type: `TimelineEvent[]` → element
|
|
544
|
+
* `TimelineEvent`, depth 1. An array type's own symbol is `Array` (builtin,
|
|
545
|
+
* filtered), so without this a `T[]` payload has NO anchor and — worse — an
|
|
546
|
+
* explicit anchor bundled for the same alias silently drops the array-ness
|
|
547
|
+
* (#306: array-vs-scalar scored compatible). The element drives the anchor
|
|
548
|
+
* symbol; the depth is reported on the `InferredType` so the bundler's
|
|
549
|
+
* existing `array_depth` wrap (#248) can restore the `[]` levels on the
|
|
550
|
+
* explicit bundle. Depth is capped at the bundler's sane ceiling; a deeper
|
|
551
|
+
* type is treated as depth 0 rather than a runaway loop.
|
|
552
|
+
*/
|
|
553
|
+
private unwrapArrayLevels;
|
|
554
|
+
/**
|
|
555
|
+
* Resolve the target node using the best available locator:
|
|
556
|
+
* 1. If span_start + span_end present → findNodeAtSpan (SWC byte offsets)
|
|
557
|
+
* 2. If expression_text present → findNodeByText (Gemini text + line)
|
|
558
|
+
* 3. Otherwise → undefined
|
|
559
|
+
*/
|
|
560
|
+
/**
|
|
561
|
+
* carrick#695: the type of the RECEIVER of a member call, plus the two facts
|
|
562
|
+
* a role decision needs about it — which package declares that type, and what
|
|
563
|
+
* the invoked member returns.
|
|
564
|
+
*
|
|
565
|
+
* Nothing here classifies. A member call with a route-shaped literal
|
|
566
|
+
* (`x.verb("/lit", arg)`) is a route registration or a request depending on
|
|
567
|
+
* what `x` IS, and that is the only question this answers. The Rust driver
|
|
568
|
+
* maps the declaring package onto the detected framework / data-fetcher
|
|
569
|
+
* lists; the compiler layer names no library and applies no shape rule.
|
|
570
|
+
*
|
|
571
|
+
* A receiver that does not resolve reports its top type (`any`) with no
|
|
572
|
+
* package, so the caller can tell "unresolved" from "declared here": on a
|
|
573
|
+
* checkout with no installed dependencies EVERY dependency-typed receiver is
|
|
574
|
+
* `any`, and treating that as workspace-owned would invent a role.
|
|
575
|
+
*/
|
|
576
|
+
private inferReceiverType;
|
|
577
|
+
/**
|
|
578
|
+
* The npm package name that declares a type, read off its declaration's file
|
|
579
|
+
* path. `undefined` when the type has no declaration to read (a top type, a
|
|
580
|
+
* primitive, an anonymous object literal) or when its declaration is not
|
|
581
|
+
* under a `node_modules` tree — a type the workspace itself declares.
|
|
582
|
+
*
|
|
583
|
+
* The LAST `node_modules` segment wins, which is what a nested or
|
|
584
|
+
* content-addressed store (`node_modules/.store/pkg@1.0.0/node_modules/pkg`)
|
|
585
|
+
* requires. Scoped names keep both segments.
|
|
586
|
+
*/
|
|
587
|
+
private declaringPackageOf;
|
|
588
|
+
/**
|
|
589
|
+
* The awaited return type of the member invoked on the receiver. Reported as
|
|
590
|
+
* a fact next to the receiver; a caller that classified on it alone would be
|
|
591
|
+
* back to a shape rule (any async helper taking a path would match).
|
|
592
|
+
*/
|
|
593
|
+
private memberReturnTypeOf;
|
|
594
|
+
/**
|
|
595
|
+
* A type's own name when it has one, else its printed form.
|
|
596
|
+
*
|
|
597
|
+
* The printed form of a type whose declaring module the use site does not
|
|
598
|
+
* import is `import("/abs/path/to/module").Name` — an absolute path that
|
|
599
|
+
* would be persisted and rendered. The name alone is both stable and the
|
|
600
|
+
* part a reader (or a package-list lookup) uses; anonymous types, top types
|
|
601
|
+
* and primitives have no name and keep their printed text.
|
|
602
|
+
*/
|
|
603
|
+
private namedTypeLabel;
|
|
604
|
+
/**
|
|
605
|
+
* A type's own name, or `an unnamed branch` when it has none.
|
|
606
|
+
*
|
|
607
|
+
* Unlike `namedTypeLabel` this never falls back to the printed form, because
|
|
608
|
+
* its output reaches a reader as a provenance `detail`, and the printed form
|
|
609
|
+
* of a type whose declaring module the use site does not import is
|
|
610
|
+
* `import("/abs/path").Name`. The bar for a detail is "one scrubbed sentence,
|
|
611
|
+
* never an absolute path", so an anonymous branch is described rather than
|
|
612
|
+
* printed.
|
|
613
|
+
*/
|
|
614
|
+
private pathFreeTypeLabel;
|
|
615
|
+
private resolveTargetNode;
|
|
616
|
+
/**
|
|
617
|
+
* Resolve to a CallExpression using the best available locator.
|
|
618
|
+
*/
|
|
619
|
+
private resolveTargetCallExpression;
|
|
620
|
+
/**
|
|
621
|
+
* Resolve to a containing function using the best available locator.
|
|
622
|
+
*/
|
|
623
|
+
private resolveContainingFunction;
|
|
624
|
+
/**
|
|
625
|
+
* Find the function whose declaration starts at (or within a couple of lines
|
|
626
|
+
* of) the given line. Used for signature inference, where the only locator is
|
|
627
|
+
* the function's start line as recorded by the scanner. Ties break toward the
|
|
628
|
+
* innermost (smallest) function. Returns undefined if nothing is close enough,
|
|
629
|
+
* to avoid binding to an unrelated function.
|
|
630
|
+
*
|
|
631
|
+
* The tolerance is one-directional in effect (carrick#766). Looking BACK is
|
|
632
|
+
* free: a function starting before the anchor is one the anchor sits inside
|
|
633
|
+
* or just after, which is what an anchor recorded on a signature or body line
|
|
634
|
+
* means. Looking FORWARD is how the anchor reaches a handler that starts a
|
|
635
|
+
* line or two into the registration or binding it names — and it is also how,
|
|
636
|
+
* unguarded, an anchor landing on a statement that declares no function
|
|
637
|
+
* (`export { action, loader };`) was answered by the NEXT declaration in the
|
|
638
|
+
* file. That answer is silent: nothing about it says it came from a
|
|
639
|
+
* neighbouring helper.
|
|
640
|
+
*
|
|
641
|
+
* So a forward candidate must be one the anchor line's own statement leads
|
|
642
|
+
* to: rejected when a statement begins at or after the anchor line and does
|
|
643
|
+
* not contain it. Nothing but trivia may sit between the anchor and the
|
|
644
|
+
* function it is taken to name. With no candidate left the caller abstains,
|
|
645
|
+
* which is the honest answer and the one a consumer check can act on.
|
|
646
|
+
*/
|
|
647
|
+
private findFunctionByLine;
|
|
648
|
+
/**
|
|
649
|
+
* Walk up from a node to find its innermost containing function.
|
|
650
|
+
*/
|
|
651
|
+
private findContainingFunctionForNode;
|
|
652
|
+
/**
|
|
653
|
+
* Follow a LINE-ONLY route-registration anchor to its handler function. The
|
|
654
|
+
* scanner sends a bare line number (no span/text) for a named-handler
|
|
655
|
+
* registration whose handler is declared away from the registration line
|
|
656
|
+
* (`InferLocator::Line`). Find a registration node — a call or object literal
|
|
657
|
+
* — that STARTS on that line and whose handler resolves, and return it.
|
|
658
|
+
* Structural (call with a function-typed arg, or handler-shaped object-literal
|
|
659
|
+
* property), no framework name-lists. Used only when the primary locator did
|
|
660
|
+
* not resolve a node, so no existing path changes.
|
|
661
|
+
*/
|
|
662
|
+
private handlerAtLine;
|
|
663
|
+
/**
|
|
664
|
+
* The route registration on `line` together with the handler it references.
|
|
665
|
+
*
|
|
666
|
+
* `handlerAtLine` returns only the handler, but the route's DECLARED contract
|
|
667
|
+
* (a validation-schema object passed alongside the handler) lives on the
|
|
668
|
+
* registration node itself, so the schema anchors need both halves.
|
|
669
|
+
*/
|
|
670
|
+
private registrationAtLine;
|
|
671
|
+
/**
|
|
672
|
+
* Follow a route-registration locator ONE indirection to the handler function.
|
|
673
|
+
*
|
|
674
|
+
* The scanner points the infer request at the registration site, but the
|
|
675
|
+
* type-bearing expression (the handler's return, or its first request read)
|
|
676
|
+
* lives inside the handler function, which the registration only *references*.
|
|
677
|
+
* This resolves that reference to the handler's `FunctionLike` node, purely
|
|
678
|
+
* structurally, in three shapes:
|
|
679
|
+
*
|
|
680
|
+
* 1. INLINE — the node is (or is inside) a call whose arguments include an
|
|
681
|
+
* arrow/function expression: `app.post('/x', async (c) => { … })`. The
|
|
682
|
+
* inline function IS the handler.
|
|
683
|
+
* 2. OBJECT-LITERAL HANDLER PROPERTY — the node is (or is inside) an object
|
|
684
|
+
* literal with a `handler`-like property whose value is either an inline
|
|
685
|
+
* function or an identifier bound to a function:
|
|
686
|
+
* `{ method, path, handler: healthCheckHandler }`. Resolve that value.
|
|
687
|
+
* 3. IDENTIFIER ARGUMENT — the node is (or is inside) a call whose 2nd+
|
|
688
|
+
* argument is an identifier bound to a function:
|
|
689
|
+
* `app.get('/x', healthCheckHandler)`. Resolve that identifier.
|
|
690
|
+
*
|
|
691
|
+
* No framework name, method name, or property name beyond the generic
|
|
692
|
+
* `handler`-shaped key is hardcoded — the match is on node SHAPE (call with a
|
|
693
|
+
* function-typed arg / object literal with a function-valued property), which
|
|
694
|
+
* is what "route registration" structurally is across Express, Fastify, Hono,
|
|
695
|
+
* Koa, a hand-rolled registry array, etc. Returns undefined when the locator
|
|
696
|
+
* is not a registration shape, so every existing non-registration path is
|
|
697
|
+
* untouched.
|
|
698
|
+
*/
|
|
699
|
+
private resolveRegisteredHandler;
|
|
700
|
+
/**
|
|
701
|
+
* Request-path gate for handler-following: return the handler function ONLY
|
|
702
|
+
* when the located node is ITSELF (after expression-unwrap) the registration
|
|
703
|
+
* — a call whose arguments include a handler function, or a route-registry
|
|
704
|
+
* object literal with a handler property. Unlike `resolveRegisteredHandler`,
|
|
705
|
+
* this does NOT walk up to an ancestor call, so a consumer's inner
|
|
706
|
+
* `JSON.stringify(payload)` (nested inside a `fetch(...)` that may carry other
|
|
707
|
+
* callbacks) is never mistaken for a registration and the existing consumer
|
|
708
|
+
* request paths stay exactly as they were.
|
|
709
|
+
*/
|
|
710
|
+
private registrationHandlerAt;
|
|
711
|
+
/**
|
|
712
|
+
* The handler function referenced by a route-registration call: the first
|
|
713
|
+
* argument that is an inline function, or an identifier bound to a function
|
|
714
|
+
* (typically the 2nd+ arg — the path literal is not function-valued, so it is
|
|
715
|
+
* skipped naturally). No callee-name check: a call whose argument resolves to
|
|
716
|
+
* a function is structurally a registration regardless of the framework.
|
|
717
|
+
*/
|
|
718
|
+
private handlerFromRegistrationCall;
|
|
719
|
+
/**
|
|
720
|
+
* The handler function carried by a route-registry object literal. Matches a
|
|
721
|
+
* property whose name is `handler`-shaped (case-insensitive `handler`) and
|
|
722
|
+
* whose value is an inline function or an identifier bound to a function.
|
|
723
|
+
*/
|
|
724
|
+
private handlerFromObjectLiteral;
|
|
725
|
+
/**
|
|
726
|
+
* Resolve a node to a handler `FunctionLike`: an inline arrow/function
|
|
727
|
+
* expression is returned directly; an identifier is followed to its binding
|
|
728
|
+
* declaration (via the compiler's definition nodes) and returned when that
|
|
729
|
+
* declaration is — or initializes to — a function. Anything else (a path
|
|
730
|
+
* literal, an object, a non-function binding) yields undefined.
|
|
731
|
+
*/
|
|
732
|
+
private asHandlerFunction;
|
|
733
|
+
/**
|
|
734
|
+
* Extract a `FunctionLike` from a declaration node the compiler resolved an
|
|
735
|
+
* identifier to: a function declaration is itself the function; a variable
|
|
736
|
+
* declaration / binding whose initializer is an inline function yields that
|
|
737
|
+
* function (`const h = async () => { … }`). Import/re-export shims are walked
|
|
738
|
+
* by ts-morph's `getDefinitionNodes`, so no manual import chasing is needed.
|
|
739
|
+
*/
|
|
740
|
+
private functionFromDeclaration;
|
|
741
|
+
/**
|
|
742
|
+
* Scan a handler function body for the FIRST request-body read that carries a
|
|
743
|
+
* type, and return that type's structural text — or null when the body holds
|
|
744
|
+
* no typed request read.
|
|
745
|
+
*
|
|
746
|
+
* "Typed request read" is matched on SHAPE, never on a callee/method name:
|
|
747
|
+
*
|
|
748
|
+
* A. a CALL that carries an explicit type argument — `c.req.json<T>()`,
|
|
749
|
+
* `parseBody<T>(req)`; the first type argument is `T`; or
|
|
750
|
+
* B. an expression with an explicit type annotation or cast in its immediate
|
|
751
|
+
* binding context — `const b: T = …`, `req.body as T`.
|
|
752
|
+
*
|
|
753
|
+
* The body is walked in source order (`forEachDescendant` is a pre-order
|
|
754
|
+
* traversal), so the FIRST such read wins, mirroring "the request type lives
|
|
755
|
+
* at the first body read" without assuming which framework produced it.
|
|
756
|
+
* Returns null (not a spurious type) for a genuinely payload-less handler.
|
|
757
|
+
*/
|
|
758
|
+
private inferRequestReadFromHandler;
|
|
759
|
+
/**
|
|
760
|
+
* Resolve a type-annotation/type-argument node to fully-structural text,
|
|
761
|
+
* dropping any `Promise<…>` wrapper (`c.req.json<T>()` returns `Promise<T>`,
|
|
762
|
+
* but the annotation node is `T` directly; the guard is harmless either way).
|
|
763
|
+
* Returns null when the node resolves to a useless/library type that carries
|
|
764
|
+
* no member shape, so the caller keeps scanning rather than locking onto
|
|
765
|
+
* `any`/`unknown`.
|
|
766
|
+
*/
|
|
767
|
+
private structuralTextFromTypeNode;
|
|
768
|
+
/**
|
|
769
|
+
* Same reduction as `structuralTextFromTypeNode`, but starting from a resolved
|
|
770
|
+
* `Type` rather than a syntax node. `at` only supplies the scope the type is
|
|
771
|
+
* rendered in.
|
|
772
|
+
*/
|
|
773
|
+
private structuralTextFromType;
|
|
774
|
+
/**
|
|
775
|
+
* The route registration a request's locator points AT, or undefined when it
|
|
776
|
+
* points at anything else.
|
|
777
|
+
*
|
|
778
|
+
* Deliberately strict: the located node must itself be the registration (a
|
|
779
|
+
* call registering a handler, or a route-registry object literal), never an
|
|
780
|
+
* ancestor of it, so an expression inside a handler is not mistaken for the
|
|
781
|
+
* route it belongs to. A locator that resolves nothing falls back to the
|
|
782
|
+
* registration on the request's line.
|
|
783
|
+
*/
|
|
784
|
+
private routeRegistrationForRequest;
|
|
785
|
+
/**
|
|
786
|
+
* Anchor (b) on the response side: the success-status contract declared in the
|
|
787
|
+
* registration's schema, as an `InferredType` ready to return. Null when the
|
|
788
|
+
* registration declares no resolvable response schema, so the caller falls
|
|
789
|
+
* back to following the handler's return.
|
|
790
|
+
*/
|
|
791
|
+
private declaredResponseInferredType;
|
|
792
|
+
/**
|
|
793
|
+
* The request contract of a route registration, in anchor order: the
|
|
794
|
+
* handler's parameter annotation (a), the registration's declared schema (b),
|
|
795
|
+
* then the first typed request read in the handler body (the pre-existing
|
|
796
|
+
* behaviour). The first anchor yielding a non-useless type wins; null means
|
|
797
|
+
* the route declares its request nowhere we can read.
|
|
798
|
+
*/
|
|
799
|
+
private requestContractFromRegistration;
|
|
800
|
+
/**
|
|
801
|
+
* Anchor (a): the request contract declared on the handler's own signature.
|
|
802
|
+
*
|
|
803
|
+
* A typed request parameter (`request: CreateWidgetRequest` where that alias
|
|
804
|
+
* resolves to a request type parameterised with the body shape) exposes the
|
|
805
|
+
* contract as the `body` member of the parameter's type. Parameters are
|
|
806
|
+
* scanned in order and the first one carrying a non-useless `body` member
|
|
807
|
+
* wins; a reply/response parameter has no `body` member, and an unparameterised
|
|
808
|
+
* request type resolves `body` to `unknown`/`any`, which the useless-type
|
|
809
|
+
* guard rejects. So a handler that declares nothing yields null and the next
|
|
810
|
+
* anchor runs.
|
|
811
|
+
*/
|
|
812
|
+
private requestBodyFromHandlerParams;
|
|
813
|
+
/**
|
|
814
|
+
* Anchor (b): the contract declared in the route's validation schema.
|
|
815
|
+
*
|
|
816
|
+
* `part` is `'body'` for the request contract and `'response'` for the
|
|
817
|
+
* response contract; a `response` entry keyed by status code resolves to its
|
|
818
|
+
* success entry. Returns null when the registration carries no schema, when
|
|
819
|
+
* the entry references something whose parsed output cannot be resolved, or
|
|
820
|
+
* when the schema is a plain JSON-schema literal (whose own object type is
|
|
821
|
+
* the JSON-Schema document, not the payload — emitting that would be worse
|
|
822
|
+
* than abstaining).
|
|
823
|
+
*/
|
|
824
|
+
private routeSchemaContractText;
|
|
825
|
+
/**
|
|
826
|
+
* The `schema` object literal carried by a route registration: scan the
|
|
827
|
+
* registration call's arguments (or the registry object literal itself) for a
|
|
828
|
+
* `schema` property whose value is an object literal.
|
|
829
|
+
*/
|
|
830
|
+
private routeSchemaObject;
|
|
831
|
+
/**
|
|
832
|
+
* The success entry of a status-keyed response map (`{ 200: …, 4xx: … }`):
|
|
833
|
+
* exact `200` when present, else the lowest 2xx key. Returns undefined when
|
|
834
|
+
* the value is not a status-keyed map, so the caller reads it as the schema
|
|
835
|
+
* itself.
|
|
836
|
+
*/
|
|
837
|
+
private successStatusEntry;
|
|
838
|
+
/**
|
|
839
|
+
* The payload type a schema entry declares.
|
|
840
|
+
*
|
|
841
|
+
* The entry is either a REFERENCE call — `ref('CreateWidget')`, a
|
|
842
|
+
* name-to-schema indirection whose registry is the argument the ref function
|
|
843
|
+
* was built from — or the schema value itself. Either way the payload is the
|
|
844
|
+
* schema's parsed output: the return type of its `parse` method (the shape
|
|
845
|
+
* every schema value exposes as its public validate-and-return API), falling
|
|
846
|
+
* back to a declared `_output` member. A value with neither is not a schema
|
|
847
|
+
* and yields null.
|
|
848
|
+
*/
|
|
849
|
+
private schemaOutputTypeText;
|
|
850
|
+
/**
|
|
851
|
+
* Follow a schema REFERENCE call (`ref('CreateWidget')`) to the schema value
|
|
852
|
+
* it names.
|
|
853
|
+
*
|
|
854
|
+
* The ref function is produced by a registry-building call — `const { $ref } =
|
|
855
|
+
* build({ CreateWidget, Widget })` — so the registry is that call's first
|
|
856
|
+
* argument, and the referenced key is a property of it. Resolve the callee to
|
|
857
|
+
* its declaration, walk to the variable declaration it is bound in, and read
|
|
858
|
+
* the key off the builder argument's type. Returns undefined for anything
|
|
859
|
+
* that is not this shape.
|
|
860
|
+
*/
|
|
861
|
+
private registrySchemaType;
|
|
862
|
+
/**
|
|
863
|
+
* The parsed output type of a schema value: the return type of its `parse`
|
|
864
|
+
* method, else a declared `_output` member. Returns undefined when neither
|
|
865
|
+
* carries a usable type — including when the schema library's own types are
|
|
866
|
+
* unavailable (an uninstalled dependency resolves the schema to `any`), which
|
|
867
|
+
* must abstain rather than publish `any` as a contract.
|
|
868
|
+
*/
|
|
869
|
+
private schemaOutputType;
|
|
870
|
+
/**
|
|
871
|
+
* Find a node by matching expression text near a target line.
|
|
872
|
+
*
|
|
873
|
+
* Strategy:
|
|
874
|
+
* 1. Get all descendant nodes within [lineNumber - searchRadius, lineNumber + searchRadius]
|
|
875
|
+
* 2. Normalize whitespace for comparison
|
|
876
|
+
* 3. Try exact match first (after normalization), prefer closest to target line
|
|
877
|
+
* 4. Fall back to substring match (LLM text in node text, or vice versa)
|
|
878
|
+
* 5. Return smallest matching node closest to target line
|
|
879
|
+
*/
|
|
880
|
+
private findNodeByText;
|
|
881
|
+
/**
|
|
882
|
+
* Find a CallExpression by matching expression text near a target line.
|
|
883
|
+
*/
|
|
884
|
+
private findCallExpressionByText;
|
|
885
|
+
/**
|
|
886
|
+
* Shared text-matching logic for node resolution.
|
|
887
|
+
* Normalizes whitespace once per candidate, then tries exact match,
|
|
888
|
+
* then substring match (preferring containing matches).
|
|
889
|
+
*/
|
|
890
|
+
private matchByText;
|
|
891
|
+
/**
|
|
892
|
+
* Pick the best match from a set of candidate nodes:
|
|
893
|
+
* smallest range, then closest to target line.
|
|
894
|
+
*/
|
|
895
|
+
private pickBestMatch;
|
|
896
|
+
/**
|
|
897
|
+
* Normalize whitespace for text comparison:
|
|
898
|
+
* collapse runs of whitespace into single spaces, trim.
|
|
899
|
+
*
|
|
900
|
+
* Also strips trailing commas before `}` `)` `]`: multi-line source
|
|
901
|
+
* literals carry them but the LLM's single-line locator print does not,
|
|
902
|
+
* and that one comma used to defeat exact AND containment matching for
|
|
903
|
+
* the payload and every enclosing node (#335). Applied symmetrically to
|
|
904
|
+
* node text and target, so both sides compare equal.
|
|
905
|
+
*
|
|
906
|
+
* Also strips the space left AFTER `(` `[` `{` by the collapse: a
|
|
907
|
+
* multi-line call whose arguments start on the next line normalizes to
|
|
908
|
+
* `f( x)` while the LLM's compact print is `f(x)`, and that one space
|
|
909
|
+
* defeated exact matching for the call and every enclosing node — the
|
|
910
|
+
* opening-delimiter mirror of the #335 trailing comma (#336). Symmetric
|
|
911
|
+
* for the same reason.
|
|
912
|
+
*/
|
|
913
|
+
private normalizeWhitespace;
|
|
914
|
+
/**
|
|
915
|
+
* Format a human-readable location string for error messages.
|
|
916
|
+
*/
|
|
917
|
+
private formatRequestLocation;
|
|
918
|
+
private findContainingFunctionBySpan;
|
|
919
|
+
private findNodeAtSpan;
|
|
920
|
+
private findCallExpressionAtSpan;
|
|
921
|
+
private unwrapPromise;
|
|
922
|
+
/**
|
|
923
|
+
* Unwrap a single `Promise<...>` / `PromiseLike<...>` type string, only when
|
|
924
|
+
* the inner text is bracket-balanced (so `Promise<A> | B` is left alone for
|
|
925
|
+
* the caller's union handling rather than mangled).
|
|
926
|
+
*/
|
|
927
|
+
private unwrapPromiseText;
|
|
928
|
+
/**
|
|
929
|
+
* Split a type string on `|` at bracket depth 0. `=>` is not treated as a
|
|
930
|
+
* closing bracket.
|
|
931
|
+
*/
|
|
932
|
+
private splitTopLevelUnion;
|
|
933
|
+
private isBracketBalanced;
|
|
934
|
+
private getNodeLocation;
|
|
935
|
+
private createInferredType;
|
|
936
|
+
private generateAlias;
|
|
937
|
+
private inferKindSuffix;
|
|
938
|
+
private log;
|
|
939
|
+
private logError;
|
|
940
|
+
}
|