carrick 0.3.84 → 0.3.86
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 +15 -0
- package/bin/carrick.mjs +89 -0
- package/dist/auth/credentials.d.ts +9 -0
- package/dist/auth/credentials.js +13 -2
- package/dist/auth/credentials.js.map +1 -1
- package/dist/auth/read.d.ts +4 -4
- package/dist/global-install.d.ts +183 -0
- package/dist/global-install.js +393 -0
- package/dist/global-install.js.map +1 -0
- package/dist/hook/post-edit.js +10 -0
- package/dist/hook/post-edit.js.map +1 -1
- package/dist/hook/session-start.js +34 -0
- package/dist/hook/session-start.js.map +1 -1
- package/dist/hook/stop.js +18 -4
- package/dist/hook/stop.js.map +1 -1
- package/dist/hook/user-prompt.js +16 -4
- package/dist/hook/user-prompt.js.map +1 -1
- package/dist/init/doctor.d.ts +40 -0
- package/dist/init/doctor.js +125 -0
- package/dist/init/doctor.js.map +1 -1
- package/dist/init/outdated.d.ts +47 -0
- package/dist/init/outdated.js +104 -0
- package/dist/init/outdated.js.map +1 -1
- package/dist/init/projects.d.ts +2 -2
- package/dist/init/run.d.ts +9 -0
- package/dist/init/run.js +57 -12
- package/dist/init/run.js.map +1 -1
- package/dist/scan.d.ts +26 -0
- package/dist/scan.js +92 -0
- package/dist/scan.js.map +1 -1
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +25 -0
- package/dist/update-check.js.map +1 -0
- package/dist/update.d.ts +128 -0
- package/dist/update.js +398 -0
- package/dist/update.js.map +1 -0
- package/package.json +7 -7
- package/sidecar/dist/src/capture/anchors.js +69 -8
- package/sidecar/dist/src/capture/api.d.ts +4 -1
- package/sidecar/dist/src/capture/deep-walk.js +4 -1
- package/sidecar/dist/src/capture/node-builder.d.ts +28 -1
- package/sidecar/dist/src/capture/node-builder.js +89 -3
- package/sidecar/dist/src/capture/unresolved.d.ts +7 -0
- package/sidecar/dist/src/capture/unresolved.js +1 -1
- package/sidecar/dist/src/type-inferrer.d.ts +128 -5
- package/sidecar/dist/src/type-inferrer.js +444 -17
- package/templates/skills/carrick-census.md +3 -1
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import ts from 'typescript';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
|
-
import { printTypeForDestination, undeclaredNamesIn } from './node-builder.js';
|
|
9
|
+
import { printTypeForDestination, substituteUndeclaredNames, undeclaredNamesIn, } from './node-builder.js';
|
|
10
10
|
import { typeIsOrContainsMachinery } from './machinery.js';
|
|
11
|
-
import { unresolvedAtAnchor } from './unresolved.js';
|
|
11
|
+
import { unresolvedAtAnchor, unresolvedSpecifiersReachableFrom, } from './unresolved.js';
|
|
12
12
|
/** Repo-root-relative source file -> extensionless specifier from entryDir. */
|
|
13
13
|
export function entryRelativeSpecifier(entryDir, repoRoot, sourceFile) {
|
|
14
14
|
const target = path
|
|
@@ -54,12 +54,25 @@ export function resolveAnchor(program, request, args) {
|
|
|
54
54
|
// (a generated model that was never generated). The stub then self-checks
|
|
55
55
|
// such a name as an error placeholder, which no walk flags. A name a
|
|
56
56
|
// sibling symbol anchor imports is resolved by that import.
|
|
57
|
+
// carrick#1377: rewrite what nothing declares to `unknown` in place, so
|
|
58
|
+
// one member typed by a module the checkout does not have stops taking
|
|
59
|
+
// every member around it down with it.
|
|
60
|
+
const rewritten = siblingSpec || !args.placeholder
|
|
61
|
+
? undefined
|
|
62
|
+
: substituteUndeclaredNamesInText(text, program, args.placeholder);
|
|
63
|
+
const aliasBody = rewritten?.text ?? text;
|
|
57
64
|
const undeclaredNames = siblingSpec || !args.placeholder
|
|
58
65
|
? []
|
|
59
|
-
: undeclaredNamesInText(
|
|
66
|
+
: undeclaredNamesInText(aliasBody, program, args.placeholder);
|
|
67
|
+
const unresolved = rewritten?.paths.length
|
|
68
|
+
? {
|
|
69
|
+
paths: rewritten.paths,
|
|
70
|
+
specifiers: unresolvedSpecifiersForLiteral(program, request.source_file, args.repoRoot),
|
|
71
|
+
}
|
|
72
|
+
: undefined;
|
|
60
73
|
return {
|
|
61
74
|
request,
|
|
62
|
-
aliasText: siblingSpec ? `import('${siblingSpec}').${text}` :
|
|
75
|
+
aliasText: siblingSpec ? `import('${siblingSpec}').${text}` : aliasBody,
|
|
63
76
|
// Literal anchors ARE the legacy-text tier (WP3 wiring of the design's
|
|
64
77
|
// structural_fallback): hand-produced type text riding the surface.
|
|
65
78
|
// The self-check still classifies decay; the fidelity metric counts
|
|
@@ -67,6 +80,7 @@ export function resolveAnchor(program, request, args) {
|
|
|
67
80
|
// ratchetable. Demotions are distinguished by failureReason.
|
|
68
81
|
serialization: 'structural_fallback',
|
|
69
82
|
...(undeclaredNames.length > 0 ? { undeclaredNames } : {}),
|
|
83
|
+
...(unresolved ? { unresolved } : {}),
|
|
70
84
|
};
|
|
71
85
|
}
|
|
72
86
|
const sourceAbs = path.join(args.repoRoot, request.source_file);
|
|
@@ -276,7 +290,12 @@ function finishInferAnchor(program, sourceFile, request, located, placeholder, r
|
|
|
276
290
|
if (!printed.text) {
|
|
277
291
|
return demote(printed.failure ?? 'node builder print failed');
|
|
278
292
|
}
|
|
279
|
-
const
|
|
293
|
+
const atAnchor = unresolvedAtAnchor(program, sourceFile, type, located);
|
|
294
|
+
// carrick#1377: a member the print named undeclared and this rewrote to
|
|
295
|
+
// `unknown` is an unresolved position too, whether or not the source type
|
|
296
|
+
// carried the compiler's placeholder at it (a bare name the print reused as
|
|
297
|
+
// written does not). Both lists feed the same labelling.
|
|
298
|
+
const unresolved = mergeUnresolved(atAnchor, printed.substitutedPaths, () => unresolvedSpecifiersReachableFrom(program, sourceFile));
|
|
280
299
|
return {
|
|
281
300
|
request,
|
|
282
301
|
aliasText: printed.text,
|
|
@@ -286,13 +305,55 @@ function finishInferAnchor(program, sourceFile, request, located, placeholder, r
|
|
|
286
305
|
...(unresolved ? { unresolved } : {}),
|
|
287
306
|
};
|
|
288
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Fold substituted member positions into what the source program could not
|
|
310
|
+
* resolve. `specifiers` is a thunk: the reachable-import walk is only worth
|
|
311
|
+
* running for an anchor that actually substituted something.
|
|
312
|
+
*/
|
|
313
|
+
function mergeUnresolved(atAnchor, substitutedPaths, specifiers) {
|
|
314
|
+
if (!substitutedPaths || substitutedPaths.length === 0)
|
|
315
|
+
return atAnchor;
|
|
316
|
+
const paths = new Set([...(atAnchor?.paths ?? []), ...substitutedPaths]);
|
|
317
|
+
return {
|
|
318
|
+
paths: [...paths],
|
|
319
|
+
specifiers: atAnchor?.specifiers ?? specifiers(),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The unresolved specifiers a LITERAL anchor's source file reaches, or none
|
|
324
|
+
* when the anchor names no file (its text names a type with nothing behind it
|
|
325
|
+
* and the detail says only that).
|
|
326
|
+
*/
|
|
327
|
+
function unresolvedSpecifiersForLiteral(program, sourceFileRel, repoRoot) {
|
|
328
|
+
if (!sourceFileRel)
|
|
329
|
+
return [];
|
|
330
|
+
const sourceFile = program.getSourceFile(path.join(repoRoot, sourceFileRel));
|
|
331
|
+
return sourceFile ? unresolvedSpecifiersReachableFrom(program, sourceFile) : [];
|
|
332
|
+
}
|
|
289
333
|
/** `undeclaredNamesIn` over type text rather than a built node. */
|
|
290
334
|
function undeclaredNamesInText(text, program, destination) {
|
|
335
|
+
const parsed = parseLiteralAnchor(text);
|
|
336
|
+
return parsed ? undeclaredNamesIn(parsed, program, destination) : [];
|
|
337
|
+
}
|
|
338
|
+
/** `substituteUndeclaredNames` over type text rather than a built node. */
|
|
339
|
+
function substituteUndeclaredNamesInText(text, program, destination) {
|
|
340
|
+
const parsed = parseLiteralAnchor(text);
|
|
341
|
+
if (!parsed)
|
|
342
|
+
return undefined;
|
|
343
|
+
const rewritten = substituteUndeclaredNames(parsed, program, destination);
|
|
344
|
+
if (rewritten.substitutions.length === 0)
|
|
345
|
+
return undefined;
|
|
346
|
+
const printer = ts.createPrinter({ removeComments: true });
|
|
347
|
+
return {
|
|
348
|
+
text: printer.printNode(ts.EmitHint.Unspecified, rewritten.node, parsed.getSourceFile()),
|
|
349
|
+
paths: rewritten.substitutions.map((entry) => entry.path),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/** The type node of `type __LiteralAnchor = <text>;`, or undefined. */
|
|
353
|
+
function parseLiteralAnchor(text) {
|
|
291
354
|
const parsed = ts.createSourceFile('literal-anchor.ts', `type __LiteralAnchor = ${text};`, ts.ScriptTarget.Latest, true);
|
|
292
355
|
const statement = parsed.statements[0];
|
|
293
|
-
|
|
294
|
-
return [];
|
|
295
|
-
return undeclaredNamesIn(statement.type, program, destination);
|
|
356
|
+
return statement && ts.isTypeAliasDeclaration(statement) ? statement.type : undefined;
|
|
296
357
|
}
|
|
297
358
|
/**
|
|
298
359
|
* True when the anchor carries a LINE and nothing else — no payload span, no
|
|
@@ -164,10 +164,13 @@ export type SelfCheckOutcome = 'ok' | 'allowlisted_external' | 'decayed_internal
|
|
|
164
164
|
* - `no_request_body`: the located request read is a validated part the
|
|
165
165
|
* route's validator binds that is not a body (a path parameter, a query), so
|
|
166
166
|
* the route states no request body contract there (carrick#1166).
|
|
167
|
+
* - `projected_value_only`: every read of a call's result takes a member out
|
|
168
|
+
* of it and none reads the value itself, so the site states a part of a
|
|
169
|
+
* payload rather than the payload a caller receives (carrick#1375).
|
|
167
170
|
* - `not_recorded`: the position carries a top type and this layer has no
|
|
168
171
|
* cause for it.
|
|
169
172
|
*/
|
|
170
|
-
export type TypeProvenanceReason = 'declared' | 'unresolved_import' | 'budget_exhausted' | 'no_payload_evidence' | 'machinery_envelope' | 'coerced_input' | 'no_success_payload' | 'no_request_body' | 'not_recorded';
|
|
173
|
+
export type TypeProvenanceReason = 'declared' | 'unresolved_import' | 'budget_exhausted' | 'no_payload_evidence' | 'machinery_envelope' | 'coerced_input' | 'no_success_payload' | 'no_request_body' | 'projected_value_only' | 'not_recorded';
|
|
171
174
|
/**
|
|
172
175
|
* One `any`/`unknown` finding inside a captured or inferred type, with its
|
|
173
176
|
* position and its cause. Sorted by `path` wherever a list is emitted, so the
|
|
@@ -266,7 +266,10 @@ export function provenanceOf(finding, unresolved) {
|
|
|
266
266
|
detail: 'the type is too deep or wide to verify within the capture budget here, so it is reported unverified rather than assumed clean',
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
|
-
|
|
269
|
+
// `unknown` reads here as well as `any` (carrick#1377): a reference nothing
|
|
270
|
+
// declares is rewritten to `unknown` at its own position, and a reader told
|
|
271
|
+
// the author declared it that way stops looking where the fix is.
|
|
272
|
+
if (unresolved?.paths.includes(finding.path)) {
|
|
270
273
|
return {
|
|
271
274
|
path: finding.path,
|
|
272
275
|
kind: finding.kind,
|
|
@@ -30,9 +30,23 @@ export interface NodeBuilderPrintResult {
|
|
|
30
30
|
failure?: string;
|
|
31
31
|
/**
|
|
32
32
|
* Names the print refers to that do not resolve at the destination in the
|
|
33
|
-
* producer's program (carrick#1165). Present only when there are some
|
|
33
|
+
* producer's program (carrick#1165). Present only when there are some —
|
|
34
|
+
* which, since carrick#1377 rewrites what it can reach, means a reference
|
|
35
|
+
* the substitution could not replace.
|
|
34
36
|
*/
|
|
35
37
|
undeclaredNames?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Member positions the print named something undeclared at, and where that
|
|
40
|
+
* reference now reads `unknown` (carrick#1377). In the walk's own path
|
|
41
|
+
* notation, so a finding at one of them can be labelled as what it is: a
|
|
42
|
+
* module that did not resolve, not a declared top type.
|
|
43
|
+
*/
|
|
44
|
+
substitutedPaths?: string[];
|
|
45
|
+
}
|
|
46
|
+
/** One reference replaced by `unknown`, with the member position it sat at. */
|
|
47
|
+
export interface UndeclaredSubstitution {
|
|
48
|
+
name: string;
|
|
49
|
+
path: string;
|
|
36
50
|
}
|
|
37
51
|
/**
|
|
38
52
|
* Print `type` as a type node anchored at `destination` (a declaration inside
|
|
@@ -40,6 +54,19 @@ export interface NodeBuilderPrintResult {
|
|
|
40
54
|
* any referenced symbol is not plainly accessible from the destination.
|
|
41
55
|
*/
|
|
42
56
|
export declare function printTypeForDestination(program: ts.Program, type: ts.Type, destination: ts.Node): NodeBuilderPrintResult;
|
|
57
|
+
/**
|
|
58
|
+
* Rewrite every reference in `node` that nothing in the producer's program
|
|
59
|
+
* declares to the `unknown` keyword, and report the member position each sat
|
|
60
|
+
* at (carrick#1377).
|
|
61
|
+
*
|
|
62
|
+
* The positions use the same notation as the capture's deep walk — `sub`,
|
|
63
|
+
* `items<0>.meta`, `[index]`, `()` for a callable return — so a path found
|
|
64
|
+
* here names the same member a self-check finding at that path names.
|
|
65
|
+
*/
|
|
66
|
+
export declare function substituteUndeclaredNames(node: ts.TypeNode, program: ts.Program, destination: ts.Node): {
|
|
67
|
+
node: ts.TypeNode;
|
|
68
|
+
substitutions: UndeclaredSubstitution[];
|
|
69
|
+
};
|
|
43
70
|
/**
|
|
44
71
|
* Bare names in a printed type node that nothing in the producer's program
|
|
45
72
|
* declares (carrick#1165).
|
|
@@ -117,10 +117,96 @@ export function printTypeForDestination(program, type, destination) {
|
|
|
117
117
|
failure: `symbols not accessible from the surface entry: ${[...new Set(inaccessible)].join(', ')}`,
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
|
+
// carrick#1377: a reference nothing declares makes the WHOLE answer
|
|
121
|
+
// unpublishable, so one member typed by a package the checkout does not
|
|
122
|
+
// have used to discard every member around it. Replace what it names with
|
|
123
|
+
// `unknown` in place and print that; the rest of the shape survives, and
|
|
124
|
+
// the positions are reported so the finding at each can be labelled as a
|
|
125
|
+
// module that did not resolve rather than a top type the author declared.
|
|
126
|
+
const substituted = substituteUndeclaredNames(node, program, destination);
|
|
120
127
|
const printer = ts.createPrinter({ removeComments: true });
|
|
121
|
-
const text = printer.printNode(ts.EmitHint.Unspecified, node, destination.getSourceFile());
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
const text = printer.printNode(ts.EmitHint.Unspecified, substituted.node, destination.getSourceFile());
|
|
129
|
+
// Asked of the REWRITTEN node: the field says what the printed answer
|
|
130
|
+
// names, so anything the substitution could not reach still fills it and
|
|
131
|
+
// still refuses publication.
|
|
132
|
+
const undeclaredNames = undeclaredNamesIn(substituted.node, program, destination);
|
|
133
|
+
const substitutedPaths = substituted.substitutions.map((entry) => entry.path);
|
|
134
|
+
return {
|
|
135
|
+
text,
|
|
136
|
+
inaccessible,
|
|
137
|
+
...(undeclaredNames.length > 0 ? { undeclaredNames } : {}),
|
|
138
|
+
...(substitutedPaths.length > 0 ? { substitutedPaths } : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Rewrite every reference in `node` that nothing in the producer's program
|
|
143
|
+
* declares to the `unknown` keyword, and report the member position each sat
|
|
144
|
+
* at (carrick#1377).
|
|
145
|
+
*
|
|
146
|
+
* The positions use the same notation as the capture's deep walk — `sub`,
|
|
147
|
+
* `items<0>.meta`, `[index]`, `()` for a callable return — so a path found
|
|
148
|
+
* here names the same member a self-check finding at that path names.
|
|
149
|
+
*/
|
|
150
|
+
export function substituteUndeclaredNames(node, program, destination) {
|
|
151
|
+
const undeclared = new Set(undeclaredNamesIn(node, program, destination));
|
|
152
|
+
if (undeclared.size === 0) {
|
|
153
|
+
return { node, substitutions: [] };
|
|
154
|
+
}
|
|
155
|
+
const substitutions = [];
|
|
156
|
+
const leftmost = (name) => ts.isIdentifier(name) ? name : leftmost(name.left);
|
|
157
|
+
// The path is carried down the visit rather than reconstructed, because a
|
|
158
|
+
// rewritten node has no parent to walk back up from.
|
|
159
|
+
const rewrite = (current, path) => {
|
|
160
|
+
if ((ts.isTypeReferenceNode(current) && undeclared.has(leftmost(current.typeName).text)) ||
|
|
161
|
+
(ts.isTypeQueryNode(current) && undeclared.has(leftmost(current.exprName).text))) {
|
|
162
|
+
substitutions.push({
|
|
163
|
+
name: ts.isTypeReferenceNode(current)
|
|
164
|
+
? leftmost(current.typeName).text
|
|
165
|
+
: leftmost(current.exprName).text,
|
|
166
|
+
path: path === '' ? '<root>' : path,
|
|
167
|
+
});
|
|
168
|
+
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
169
|
+
}
|
|
170
|
+
return ts.visitEachChild(current, (child) => rewrite(child, childPath(current, child, path)),
|
|
171
|
+
/* context */ undefined);
|
|
172
|
+
};
|
|
173
|
+
const rewritten = rewrite(node, '');
|
|
174
|
+
return { node: rewritten, substitutions };
|
|
175
|
+
}
|
|
176
|
+
/** The deep walk's path for `child` inside `parent`, extending `path`. */
|
|
177
|
+
function childPath(parent, child, path) {
|
|
178
|
+
if (ts.isPropertySignature(parent) && parent.type === child) {
|
|
179
|
+
const name = ts.isIdentifier(parent.name) || ts.isStringLiteral(parent.name)
|
|
180
|
+
? parent.name.text
|
|
181
|
+
: parent.name.getText?.() ?? '';
|
|
182
|
+
return path === '' ? name : `${path}.${name}`;
|
|
183
|
+
}
|
|
184
|
+
if (ts.isArrayTypeNode(parent) && parent.elementType === child) {
|
|
185
|
+
return `${path}<0>`;
|
|
186
|
+
}
|
|
187
|
+
if (ts.isIndexSignatureDeclaration(parent) && parent.type === child) {
|
|
188
|
+
return `${path}[index]`;
|
|
189
|
+
}
|
|
190
|
+
if ((ts.isFunctionTypeNode(parent) ||
|
|
191
|
+
ts.isMethodSignature(parent) ||
|
|
192
|
+
ts.isCallSignatureDeclaration(parent)) &&
|
|
193
|
+
parent.type === child) {
|
|
194
|
+
return `${path}()`;
|
|
195
|
+
}
|
|
196
|
+
if (ts.isTypeReferenceNode(parent) && parent.typeArguments) {
|
|
197
|
+
const index = parent.typeArguments.indexOf(child);
|
|
198
|
+
if (index >= 0)
|
|
199
|
+
return `${path}<${index}>`;
|
|
200
|
+
}
|
|
201
|
+
if (ts.isTupleTypeNode(parent)) {
|
|
202
|
+
const index = parent.elements.indexOf(child);
|
|
203
|
+
if (index >= 0)
|
|
204
|
+
return `${path}<${index}>`;
|
|
205
|
+
}
|
|
206
|
+
// A union or intersection member sits at its parent's position, as the deep
|
|
207
|
+
// walk records it; everything else (a type literal's members, a parenthesis)
|
|
208
|
+
// keeps the path it was reached with.
|
|
209
|
+
return path;
|
|
124
210
|
}
|
|
125
211
|
/**
|
|
126
212
|
* Bare names in a printed type node that nothing in the producer's program
|
|
@@ -26,3 +26,10 @@ import { type UnresolvedAtAnchor } from './deep-walk.js';
|
|
|
26
26
|
* depth prints `import('./m').Row[]`, whose members sit under `<0>`.
|
|
27
27
|
*/
|
|
28
28
|
export declare function unresolvedAtAnchor(program: ts.Program, sourceFile: ts.SourceFile, type: ts.Type, location: ts.Node, pathPrefix?: string): UnresolvedAtAnchor | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Module specifiers, as written, that do not resolve from `sourceFile` or from
|
|
31
|
+
* any source module it imports, breadth-first so the nearest come first, with
|
|
32
|
+
* relative specifiers ahead of package names. Installed packages and the
|
|
33
|
+
* default library are not descended into.
|
|
34
|
+
*/
|
|
35
|
+
export declare function unresolvedSpecifiersReachableFrom(program: ts.Program, sourceFile: ts.SourceFile): string[];
|
|
@@ -50,7 +50,7 @@ function prefixPath(prefix, path) {
|
|
|
50
50
|
* relative specifiers ahead of package names. Installed packages and the
|
|
51
51
|
* default library are not descended into.
|
|
52
52
|
*/
|
|
53
|
-
function unresolvedSpecifiersReachableFrom(program, sourceFile) {
|
|
53
|
+
export function unresolvedSpecifiersReachableFrom(program, sourceFile) {
|
|
54
54
|
let cache = reachableCache.get(program);
|
|
55
55
|
if (!cache) {
|
|
56
56
|
cache = new Map();
|
|
@@ -206,17 +206,131 @@ export declare class TypeInferrer {
|
|
|
206
206
|
*/
|
|
207
207
|
private isResponseSend;
|
|
208
208
|
/**
|
|
209
|
-
* The
|
|
209
|
+
* The call `node` is an ARGUMENT of, looking through the wrappers that do
|
|
210
210
|
* not change a payload (parentheses, `as`, `satisfies`, `!`, `await`) and a
|
|
211
|
-
* `JSON.stringify` around the body. `undefined` when
|
|
212
|
-
*
|
|
211
|
+
* `JSON.stringify` around the body. `undefined` when `accept` rejects that
|
|
212
|
+
* call or `node` is its callee.
|
|
213
|
+
*
|
|
214
|
+
* Two readings use it: a value handed to a response send is the payload that
|
|
215
|
+
* send transmits, and a body read handed to a call that states what it
|
|
216
|
+
* returns is a better statement of that body than the read (carrick#1382).
|
|
213
217
|
*/
|
|
214
|
-
private
|
|
218
|
+
private receivingCallOf;
|
|
215
219
|
private inferCallResult;
|
|
216
220
|
private inferVariable;
|
|
217
221
|
private inferExpression;
|
|
218
222
|
private inferRequestBody;
|
|
219
223
|
private resolveCallResultTerminalNode;
|
|
224
|
+
/**
|
|
225
|
+
* True when a member read on the call's result resolves to one of that
|
|
226
|
+
* result's own TYPE ARGUMENTS — the source is unwrapping a generic envelope
|
|
227
|
+
* by hand (`state.data` off a `ResourceState<Envelope>`), and the payload it
|
|
228
|
+
* carries is the instantiation, not the envelope (carrick#1375).
|
|
229
|
+
*
|
|
230
|
+
* The generic is what tells the two apart. A call that answers its payload
|
|
231
|
+
* directly is read member by member too, and its declared result IS the
|
|
232
|
+
* contract; abstaining there would throw away the type the request boundary
|
|
233
|
+
* states, which a replay over a real repo's consumer rows showed on a
|
|
234
|
+
* `{ ok: true } | { ok: false; reason: string }` result read as `sent.ok`.
|
|
235
|
+
*/
|
|
236
|
+
private projectionReadsGenericPayload;
|
|
237
|
+
/**
|
|
238
|
+
* The payload a RESULT CARRIER carries, or `undefined` when `type` is not
|
|
239
|
+
* one or its success side cannot be told from its failure side
|
|
240
|
+
* (carrick#1376).
|
|
241
|
+
*
|
|
242
|
+
* A carrier is recognised by its shape, never by a name: a union of object
|
|
243
|
+
* branches, instantiated with two or more type arguments, at least one of
|
|
244
|
+
* which a branch holds as a member. `Result<T, E>`, `Either<L, R>` and a
|
|
245
|
+
* hand-rolled `{ ok: true; value: T } | { ok: false; error: E }` are all the
|
|
246
|
+
* same shape, and a promise-like around one is peeled first through the
|
|
247
|
+
* language's own await protocol. A single generic object — a resource state,
|
|
248
|
+
* a query result — is NOT a union and is left to carrick#1375, which
|
|
249
|
+
* abstains on it so a sibling site can answer.
|
|
250
|
+
*
|
|
251
|
+
* Which argument is the payload is decided twice over, and never guessed:
|
|
252
|
+
*
|
|
253
|
+
* 1. the platform's error shape. Exactly one argument that is not
|
|
254
|
+
* error-shaped, beside at least one that is, is the success side.
|
|
255
|
+
* 2. what the source reads. Where every argument looks alike — `Pair<A,
|
|
256
|
+
* string>` — a member read of the carrier that resolves to exactly one
|
|
257
|
+
* of the arguments names the side this call site takes.
|
|
258
|
+
*
|
|
259
|
+
* Where neither decides, the carrier keeps its own answer and the limit is
|
|
260
|
+
* logged: a coin flip published as a contract is worse than an envelope a
|
|
261
|
+
* reader can see is an envelope.
|
|
262
|
+
*/
|
|
263
|
+
private resultCarrierPayload;
|
|
264
|
+
/**
|
|
265
|
+
* `Future<T>` -> `T` for a promise-like of the source's own making, read off
|
|
266
|
+
* the await protocol rather than a name: a `then` whose first parameter is a
|
|
267
|
+
* callback, whose own first parameter is the value awaiting it yields.
|
|
268
|
+
* `Promise` and `PromiseLike` are peeled by `unwrapPromiseType` before this.
|
|
269
|
+
*/
|
|
270
|
+
private unwrapThenableType;
|
|
271
|
+
/**
|
|
272
|
+
* The platform's error shape, in full: `name` and `message` strings AND a
|
|
273
|
+
* `stack`, which is what the `Error` interface declares and every subclass
|
|
274
|
+
* of it inherits.
|
|
275
|
+
*
|
|
276
|
+
* `stack` is what makes the test a test. A name and a message alone are a
|
|
277
|
+
* shape a PAYLOAD can have — a contact form declares both — and reading such
|
|
278
|
+
* a payload as the failure side would publish the other argument, which is
|
|
279
|
+
* the concrete-but-wrong answer this whole rule exists to avoid. A union is
|
|
280
|
+
* error-shaped when every member of it is.
|
|
281
|
+
*/
|
|
282
|
+
private isErrorShaped;
|
|
283
|
+
/**
|
|
284
|
+
* The member read that takes `identifier` as its RECEIVER — `query` in
|
|
285
|
+
* `query.data`, `envelope` in `envelope.list[0]` — or `undefined` when the
|
|
286
|
+
* identifier names the value itself.
|
|
287
|
+
*
|
|
288
|
+
* A member CALL is not a projection: `res.text()` yields a body rather than
|
|
289
|
+
* a part of one, and what it returns stays the walk's business. The
|
|
290
|
+
* zero-argument json body read has its own branch and is taken before this
|
|
291
|
+
* is asked.
|
|
292
|
+
*/
|
|
293
|
+
private projectionOnReceiver;
|
|
294
|
+
/**
|
|
295
|
+
* The call that CONSUMES this json body read and states what the body is —
|
|
296
|
+
* `parseEnvelope(await response.json())` — or `undefined` when nothing
|
|
297
|
+
* downstream of the read says more about it than the read itself does
|
|
298
|
+
* (carrick#1382).
|
|
299
|
+
*
|
|
300
|
+
* Three conditions, all shapes of the language rather than names:
|
|
301
|
+
*
|
|
302
|
+
* - the read reaches the call as an ARGUMENT, through the wrappers that do
|
|
303
|
+
* not change a value (`await`, parentheses, `as`, `satisfies`, `!`). A
|
|
304
|
+
* cast with no call around it therefore keeps the read as the terminal,
|
|
305
|
+
* so `(await res.json()) as Entry` is still read off the read itself;
|
|
306
|
+
* - the call's own result is BOUND — declared into a variable, returned, or
|
|
307
|
+
* assigned — so a call the source made for its side effect
|
|
308
|
+
* (`store(await res.json())`) states nothing about the payload;
|
|
309
|
+
* - that result is an OBJECT shape. A validator answering `boolean` or a
|
|
310
|
+
* serialiser answering `string` describes what the caller did with the
|
|
311
|
+
* body, not what the body is, and publishing it would be a
|
|
312
|
+
* concrete-but-wrong contract where the honest `any` of the read is
|
|
313
|
+
* merely unresolved.
|
|
314
|
+
*/
|
|
315
|
+
private statedPayloadAroundBodyRead;
|
|
316
|
+
/**
|
|
317
|
+
* The source keeps this call's result: it initializes a declaration, is
|
|
318
|
+
* returned, is assigned, or is an arrow's expression body. A result that is
|
|
319
|
+
* kept is one the source has a use for; a discarded one is a side effect.
|
|
320
|
+
*/
|
|
321
|
+
private callResultIsBound;
|
|
322
|
+
/**
|
|
323
|
+
* A shape a JSON body can be: an object, an array, or a union of them.
|
|
324
|
+
* Top types, primitives, `void` and callables are not.
|
|
325
|
+
*/
|
|
326
|
+
private isObjectShape;
|
|
327
|
+
/**
|
|
328
|
+
* Every use of a tracked name inside `expr` reads a member out of the
|
|
329
|
+
* tracked value, so the expression's type describes a PART of the payload.
|
|
330
|
+
* False when the expression uses no tracked name at all, so a caller can
|
|
331
|
+
* read it as "this is a projection" rather than "this is not a use".
|
|
332
|
+
*/
|
|
333
|
+
private usesNamesOnlyByProjection;
|
|
220
334
|
private extractBindingFromCall;
|
|
221
335
|
private extractBindingNames;
|
|
222
336
|
private getPrimaryBindingNode;
|
|
@@ -594,7 +708,16 @@ export declare class TypeInferrer {
|
|
|
594
708
|
* True when an object literal argument is response INIT rather than a body:
|
|
595
709
|
* every property it declares is one the standard `ResponseInit` declares
|
|
596
710
|
* (`status`, `statusText`, `headers`), and it states at least one of them as
|
|
597
|
-
* init really does — a
|
|
711
|
+
* init really does — a status in the HTTP range, or headers.
|
|
712
|
+
*
|
|
713
|
+
* The status does NOT have to be a literal code. A route that carries its
|
|
714
|
+
* outcome in a value writes `new Response(body, { status: result.status })`,
|
|
715
|
+
* where the source fixes no code and `statedStatusCodes` answers
|
|
716
|
+
* `'variable'` — status-shaped, just not pinned. Requiring a literal there
|
|
717
|
+
* left the whole init object reading as a body, so an endpoint whose payload
|
|
718
|
+
* this layer does not publish (a string body) published `{ status: number }`
|
|
719
|
+
* as its response contract instead: a wrong contract, served, where an
|
|
720
|
+
* abstention was the honest answer.
|
|
598
721
|
*
|
|
599
722
|
* A payload that merely has a `status` member of its own (`{ status: "ok",
|
|
600
723
|
* service: "ledger" }`) declares members init does not, or states `status` as
|