carrick 0.3.114 → 0.3.116
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/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/bundler.d.ts +17 -1
- package/sidecar/dist/src/bundler.js +8 -5
- package/sidecar/dist/src/capture/anchors.js +8 -1
- package/sidecar/dist/src/capture/api.d.ts +7 -2
- package/sidecar/dist/src/capture/check-classify.d.ts +3 -1
- package/sidecar/dist/src/capture/check-classify.js +47 -3
- package/sidecar/dist/src/capture/check-deep.d.ts +11 -1
- package/sidecar/dist/src/capture/check-deep.js +14 -1
- package/sidecar/dist/src/capture/check-probe.d.ts +29 -1
- package/sidecar/dist/src/capture/check-probe.js +50 -1
- package/sidecar/dist/src/capture/check.js +20 -3
- package/sidecar/dist/src/capture/index.d.ts +1 -1
- package/sidecar/dist/src/capture/index.js +35 -5
- package/sidecar/dist/src/capture/status-table.d.ts +17 -0
- package/sidecar/dist/src/capture/status-table.js +26 -0
- package/sidecar/dist/src/index.js +1 -1
- package/sidecar/dist/src/symbol-relation.d.ts +36 -0
- package/sidecar/dist/src/symbol-relation.js +109 -0
- package/sidecar/dist/src/type-inferrer.d.ts +118 -3
- package/sidecar/dist/src/type-inferrer.js +481 -18
- package/sidecar/dist/src/types.d.ts +38 -0
- package/sidecar/dist/src/unwidened.js +22 -0
- package/sidecar/dist/src/validators.d.ts +58 -12
- package/sidecar/dist/src/validators.js +7 -0
|
@@ -322,7 +322,7 @@ function handleInfer(request) {
|
|
|
322
322
|
const results = [...byProject(request.requests, (item) => item.file_path)].map(([key, items]) => projectComponents(key).typeInferrer.infer(items, request.extraction_config, () => {
|
|
323
323
|
done += 1;
|
|
324
324
|
report();
|
|
325
|
-
}));
|
|
325
|
+
}, request.model_symbols));
|
|
326
326
|
const result = results.length === 1
|
|
327
327
|
? results[0]
|
|
328
328
|
: (() => {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a type the model named for a producer's response relates to the
|
|
3
|
+
* compiler's type of what the handler sends (carrick#2111).
|
|
4
|
+
*
|
|
5
|
+
* The model names one symbol per row, and by schema it is a bare name. The
|
|
6
|
+
* compiler reads the response expression itself. Where the two are the same
|
|
7
|
+
* type the name is the better thing to publish; where they are not, the name
|
|
8
|
+
* is wrong about the body: an element named for a list, a helper's input type
|
|
9
|
+
* named for its result, a type named for the wrapper sent around it.
|
|
10
|
+
*
|
|
11
|
+
* The compiler decides, by assignability asked both ways. Neither names nor
|
|
12
|
+
* printed text are compared.
|
|
13
|
+
*/
|
|
14
|
+
import { ts } from 'ts-morph';
|
|
15
|
+
import type { ModelSymbolRelation } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* The relation of `symbol` (the type the model named) to `read` (the types
|
|
18
|
+
* of the expressions the handler sends, one per surviving branch), or
|
|
19
|
+
* `undefined` where there is nothing to compare: either side is `any` or
|
|
20
|
+
* `unknown` at its root, or is only `null`.
|
|
21
|
+
*
|
|
22
|
+
* - `same`: the two are one type. A list's own `readonly` does not count.
|
|
23
|
+
* - `list`: the read is a list of the symbol, `depth` levels deep.
|
|
24
|
+
* - `member`: the symbol is one member of the read's union, and the rest
|
|
25
|
+
* is more than `null` or `undefined`. Which member is the success body is
|
|
26
|
+
* decided by status, elsewhere.
|
|
27
|
+
* - `nullable`: the read is the symbol or `null`/`undefined`.
|
|
28
|
+
* - `different`: anything else.
|
|
29
|
+
*/
|
|
30
|
+
export declare function relateSymbolToRead(checker: ts.TypeChecker, symbol: ts.Type, read: readonly ts.Type[]): ModelSymbolRelation | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Whether a printed type holds `any` or `unknown` anywhere. Such a read has
|
|
33
|
+
* a part the compiler could not see, and assignability against it says
|
|
34
|
+
* nothing about the part it could.
|
|
35
|
+
*/
|
|
36
|
+
export declare function printedTypeHoldsTopType(text: string): boolean;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a type the model named for a producer's response relates to the
|
|
3
|
+
* compiler's type of what the handler sends (carrick#2111).
|
|
4
|
+
*
|
|
5
|
+
* The model names one symbol per row, and by schema it is a bare name. The
|
|
6
|
+
* compiler reads the response expression itself. Where the two are the same
|
|
7
|
+
* type the name is the better thing to publish; where they are not, the name
|
|
8
|
+
* is wrong about the body: an element named for a list, a helper's input type
|
|
9
|
+
* named for its result, a type named for the wrapper sent around it.
|
|
10
|
+
*
|
|
11
|
+
* The compiler decides, by assignability asked both ways. Neither names nor
|
|
12
|
+
* printed text are compared.
|
|
13
|
+
*/
|
|
14
|
+
import { ts } from 'ts-morph';
|
|
15
|
+
/** A list level deeper than this is not a payload shape worth following. */
|
|
16
|
+
const MAX_LIST_DEPTH = 10;
|
|
17
|
+
const NULLISH = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;
|
|
18
|
+
const TOP = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
|
|
19
|
+
function partsOf(types) {
|
|
20
|
+
const members = [];
|
|
21
|
+
let nullish = false;
|
|
22
|
+
for (const type of types) {
|
|
23
|
+
for (const part of type.isUnion() ? type.types : [type]) {
|
|
24
|
+
if (part.flags & NULLISH)
|
|
25
|
+
nullish = true;
|
|
26
|
+
else if (!members.includes(part))
|
|
27
|
+
members.push(part);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { members, nullish };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `type` with its list levels peeled, whichever of `T[]`, `readonly T[]` and
|
|
34
|
+
* a tuple each level is. JSON has no `readonly`, so a list's own modifier is set
|
|
35
|
+
* aside; one deeper inside the element is the wire transform's to drop.
|
|
36
|
+
*/
|
|
37
|
+
function peelLists(checker, type) {
|
|
38
|
+
let element = type;
|
|
39
|
+
let depth = 0;
|
|
40
|
+
while (depth < MAX_LIST_DEPTH && (checker.isArrayType(element) || checker.isTupleType(element))) {
|
|
41
|
+
// A tuple is a list on the wire: its element is every member's type.
|
|
42
|
+
const inner = checker.getIndexTypeOfType(element, ts.IndexKind.Number);
|
|
43
|
+
if (!inner)
|
|
44
|
+
break;
|
|
45
|
+
element = inner;
|
|
46
|
+
depth += 1;
|
|
47
|
+
}
|
|
48
|
+
return { element, depth };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The relation of `symbol` (the type the model named) to `read` (the types
|
|
52
|
+
* of the expressions the handler sends, one per surviving branch), or
|
|
53
|
+
* `undefined` where there is nothing to compare: either side is `any` or
|
|
54
|
+
* `unknown` at its root, or is only `null`.
|
|
55
|
+
*
|
|
56
|
+
* - `same`: the two are one type. A list's own `readonly` does not count.
|
|
57
|
+
* - `list`: the read is a list of the symbol, `depth` levels deep.
|
|
58
|
+
* - `member`: the symbol is one member of the read's union, and the rest
|
|
59
|
+
* is more than `null` or `undefined`. Which member is the success body is
|
|
60
|
+
* decided by status, elsewhere.
|
|
61
|
+
* - `nullable`: the read is the symbol or `null`/`undefined`.
|
|
62
|
+
* - `different`: anything else.
|
|
63
|
+
*/
|
|
64
|
+
export function relateSymbolToRead(checker, symbol, read) {
|
|
65
|
+
if (read.length === 0)
|
|
66
|
+
return undefined;
|
|
67
|
+
if ([symbol, ...read].some((type) => type.flags & TOP))
|
|
68
|
+
return undefined;
|
|
69
|
+
const sent = partsOf(read);
|
|
70
|
+
const named = partsOf([symbol]);
|
|
71
|
+
if (sent.members.length === 0 || named.members.length === 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
const same = (a, b) => checker.isTypeAssignableTo(a, b) && checker.isTypeAssignableTo(b, a);
|
|
74
|
+
/** One type, up to `readonly` on its list levels. */
|
|
75
|
+
const sameBody = (a, b) => {
|
|
76
|
+
if (same(a, b))
|
|
77
|
+
return true;
|
|
78
|
+
const left = peelLists(checker, a);
|
|
79
|
+
const right = peelLists(checker, b);
|
|
80
|
+
return left.depth > 0 && left.depth === right.depth && same(left.element, right.element);
|
|
81
|
+
};
|
|
82
|
+
const namedAreSent = named.members.every((n) => sent.members.some((s) => sameBody(s, n)));
|
|
83
|
+
const sentAreNamed = sent.members.every((s) => named.members.some((n) => sameBody(s, n)));
|
|
84
|
+
if (namedAreSent && sentAreNamed) {
|
|
85
|
+
if (sent.nullish === named.nullish)
|
|
86
|
+
return { relation: 'same' };
|
|
87
|
+
return { relation: sent.nullish ? 'nullable' : 'different' };
|
|
88
|
+
}
|
|
89
|
+
if (namedAreSent && (sent.nullish || !named.nullish)) {
|
|
90
|
+
return { relation: 'member' };
|
|
91
|
+
}
|
|
92
|
+
if (sent.members.length === 1 && !sent.nullish && !named.nullish) {
|
|
93
|
+
const list = peelLists(checker, sent.members[0]);
|
|
94
|
+
const element = named.members.length === 1 ? peelLists(checker, symbol) : { element: symbol, depth: 0 };
|
|
95
|
+
if (list.depth > element.depth && same(list.element, element.element)) {
|
|
96
|
+
return { relation: 'list', depth: list.depth - element.depth };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { relation: 'different' };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether a printed type holds `any` or `unknown` anywhere. Such a read has
|
|
103
|
+
* a part the compiler could not see, and assignability against it says
|
|
104
|
+
* nothing about the part it could.
|
|
105
|
+
*/
|
|
106
|
+
export function printedTypeHoldsTopType(text) {
|
|
107
|
+
const withoutLiterals = text.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/g, "''");
|
|
108
|
+
return /\b(?:any|unknown)\b/.test(withoutLiterals);
|
|
109
|
+
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* function's return type.
|
|
19
19
|
*/
|
|
20
20
|
import { Project, SourceFile, type CallExpression } from 'ts-morph';
|
|
21
|
-
import type { InferRequestItem, InferResult, ExtractionConfig } from './types.js';
|
|
21
|
+
import type { InferRequestItem, InferResult, ModelSymbol, ExtractionConfig } from './types.js';
|
|
22
22
|
/**
|
|
23
23
|
* Strongly-discriminating member names of HTTP transport machinery — the
|
|
24
24
|
* fetch/DOM `Response` & `Request`, a Node `http.ServerResponse`, a framework
|
|
@@ -123,7 +123,33 @@ export declare class TypeInferrer {
|
|
|
123
123
|
* @param onRequestDone - Called once per request, as the batch is done with it
|
|
124
124
|
* @returns InferResult with inferred types or errors
|
|
125
125
|
*/
|
|
126
|
-
infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig, onRequestDone?: InferRequestDone): InferResult;
|
|
126
|
+
infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig, onRequestDone?: InferRequestDone, modelSymbols?: ModelSymbol[]): InferResult;
|
|
127
|
+
/** The types the model named, by the alias of the row each is for (carrick#2111). */
|
|
128
|
+
private modelSymbols;
|
|
129
|
+
/**
|
|
130
|
+
* carrick#2111: where the model named a type for the response this request
|
|
131
|
+
* reads, record on `inferred` how that type relates to the compiler's type
|
|
132
|
+
* of what the handler sends (`read`, one type per surviving branch). The
|
|
133
|
+
* driver keeps, re-depths or drops the model's symbol on the answer.
|
|
134
|
+
*
|
|
135
|
+
* Nothing is recorded, and the symbol stands, where there is nothing to
|
|
136
|
+
* compare: the symbol does not resolve in this program, or the published
|
|
137
|
+
* read holds `any` or `unknown` somewhere.
|
|
138
|
+
*/
|
|
139
|
+
private relateModelSymbol;
|
|
140
|
+
/**
|
|
141
|
+
* `func` is the function the row's line names: it opens on that line (a
|
|
142
|
+
* method under its decorators, an exported handler), or it is the handler
|
|
143
|
+
* of the registration that opens there (`app.post<…>(\n '/path',\n async
|
|
144
|
+
* (request) => { … })`, or a named handler declared elsewhere).
|
|
145
|
+
*/
|
|
146
|
+
private isTheRowsOwnFunction;
|
|
147
|
+
/**
|
|
148
|
+
* The declared type of a symbol the model named, found as the bundle finds
|
|
149
|
+
* it (through a barrel's re-exports), in a file this program already
|
|
150
|
+
* holds. A name that declares a value and no type resolves to nothing.
|
|
151
|
+
*/
|
|
152
|
+
private modelSymbolType;
|
|
127
153
|
/**
|
|
128
154
|
* carrick#1836: list on `result` the declarations behind the names its text
|
|
129
155
|
* prints that the request's file cannot resolve (`PrintedTypes.namesIn`).
|
|
@@ -423,6 +449,36 @@ export declare class TypeInferrer {
|
|
|
423
449
|
private inferExpression;
|
|
424
450
|
private inferRequestBody;
|
|
425
451
|
private resolveCallResultTerminalNode;
|
|
452
|
+
/**
|
|
453
|
+
* Where the walk ends on the call's own destructuring binding (`const {
|
|
454
|
+
* tasks } = await api.list()`), the declaration has no type of its own: the
|
|
455
|
+
* checker answers `any` for it. The value it takes apart is the call's, so
|
|
456
|
+
* the call answers (carrick#2112). Not for a transport value: a helper's
|
|
457
|
+
* `{ body, response }` read as text would bypass the capture's machinery
|
|
458
|
+
* guard, so that binding stays unread and the capture answers the row.
|
|
459
|
+
*/
|
|
460
|
+
private destructuredValueTerminal;
|
|
461
|
+
/**
|
|
462
|
+
* `expr` declares a name for what a call returns when it is handed a
|
|
463
|
+
* tracked name whole (`const data: T = await parseJson(response, 'op')`).
|
|
464
|
+
*/
|
|
465
|
+
private handsTrackedValueToACall;
|
|
466
|
+
/** `expr` holds a binary whole-body read taken on a tracked name. */
|
|
467
|
+
private holdsBinaryBodyRead;
|
|
468
|
+
/**
|
|
469
|
+
* `func`, the function a call is made in, is the executor of the promise
|
|
470
|
+
* `returnStmt` returns: `return new Promise<T>((resolve, reject) => { … })`
|
|
471
|
+
* (carrick#2282). The promise is recognised by the language's await
|
|
472
|
+
* protocol on the constructed value, never by a name.
|
|
473
|
+
*/
|
|
474
|
+
private callIsInReturnedPromiseExecutor;
|
|
475
|
+
/**
|
|
476
|
+
* `expr` declares a name for the tracked value as it is: its initializer,
|
|
477
|
+
* with the wrappers that leave a value what it was peeled (parentheses,
|
|
478
|
+
* `await`, `as`, `satisfies`, `!`), is a tracked name (carrick#2112). The
|
|
479
|
+
* declaration's type is then the value's, as the source restates it.
|
|
480
|
+
*/
|
|
481
|
+
private restatesTrackedValue;
|
|
426
482
|
/**
|
|
427
483
|
* True when a member read on the call's result resolves to one of that
|
|
428
484
|
* result's own TYPE ARGUMENTS — the source is unwrapping a generic envelope
|
|
@@ -473,6 +529,35 @@ export declare class TypeInferrer {
|
|
|
473
529
|
* as written.
|
|
474
530
|
*/
|
|
475
531
|
private resultCarrierPayload;
|
|
532
|
+
/**
|
|
533
|
+
* carrick#1841: the branches of a conditional type the compiler left
|
|
534
|
+
* deferred on a type parameter, each with its promise-like peeled, or
|
|
535
|
+
* `undefined` for any other type.
|
|
536
|
+
*
|
|
537
|
+
* Inside a generic function whose result depends on a type parameter it has
|
|
538
|
+
* not fixed (`<Throw extends boolean>(o) => client.get<Table, Errors,
|
|
539
|
+
* Throw>(…)`), the call's result is such a conditional. The compiler states
|
|
540
|
+
* what its branches allow itself: the constraint of the conditional, which
|
|
541
|
+
* is its apparent type. No branch is read off the alias's declaration.
|
|
542
|
+
*/
|
|
543
|
+
private deferredConditionalBranches;
|
|
544
|
+
/**
|
|
545
|
+
* carrick#1841: what a call result deferred on a type parameter carries,
|
|
546
|
+
* read through its branches. `undefined` where `type` is no such
|
|
547
|
+
* conditional, or none of its branches is a result carrier, and the type
|
|
548
|
+
* keeps the answer it had.
|
|
549
|
+
*
|
|
550
|
+
* Each branch that is a carrier is read by the carrier read, with the
|
|
551
|
+
* call's own type arguments. Every one of them must name the same payload,
|
|
552
|
+
* by the compiler's type identity, and every other branch must hold that
|
|
553
|
+
* payload as the type of one of its members (the branch a client answers
|
|
554
|
+
* with when it throws on failure holds the body and no error side). Then
|
|
555
|
+
* the payload is the body whichever branch the caller fixes. A carrier
|
|
556
|
+
* branch that states no contract decides for the whole. Branches that
|
|
557
|
+
* disagree state none: the conditional is the client's bookkeeping and is
|
|
558
|
+
* never published as written.
|
|
559
|
+
*/
|
|
560
|
+
private deferredConditionalPayload;
|
|
476
561
|
/**
|
|
477
562
|
* The value of a response table's one success row, for a table a carrier
|
|
478
563
|
* holds as itself rather than through its body. `undefined` where the table
|
|
@@ -661,7 +746,8 @@ export declare class TypeInferrer {
|
|
|
661
746
|
* The zero-argument whole-body read that takes `receiver` as its receiver,
|
|
662
747
|
* `res.json()` or `res.text()`, or `undefined`. A text read is a body read
|
|
663
748
|
* like a json one (carrick#1842): without it, `return res.text()` left the
|
|
664
|
-
* walk on the response binding and published the transport object.
|
|
749
|
+
* walk on the response binding and published the transport object. `reads`
|
|
750
|
+
* names the reads asked for.
|
|
665
751
|
*/
|
|
666
752
|
private bodyReadOnReceiver;
|
|
667
753
|
private collectDefUseNodes;
|
|
@@ -1629,6 +1715,35 @@ export declare class TypeInferrer {
|
|
|
1629
1715
|
* by ts-morph's `getDefinitionNodes`, so no manual import chasing is needed.
|
|
1630
1716
|
*/
|
|
1631
1717
|
private functionFromDeclaration;
|
|
1718
|
+
/**
|
|
1719
|
+
* True when `handler` reads nothing from the incoming message (carrick#827):
|
|
1720
|
+
* every use of one of its parameters, named or destructured, is the receiver
|
|
1721
|
+
* of a call chain whose value the handler discards or returns
|
|
1722
|
+
* (`reply.json(result)`, `return c.status(201).json(result)`). That is a
|
|
1723
|
+
* send. A returned chain must end in a call that takes an argument, so a
|
|
1724
|
+
* read handed straight back (`return request.json()`) is not one.
|
|
1725
|
+
*
|
|
1726
|
+
* Anything else may be a read of the body and makes this false: a property
|
|
1727
|
+
* read, a call whose value is used, a parameter handed whole to another
|
|
1728
|
+
* function, or a call in the chain handed an argument that can receive what
|
|
1729
|
+
* the parameter gives (`argumentCanReceive`: a callback, an object with a
|
|
1730
|
+
* method), as in `request.pipe(dest)`, `request.on('data', cb)` and
|
|
1731
|
+
* `request.json().then(cb)`. No method or property name is consulted. A
|
|
1732
|
+
* handler with no parameter, or one that uses `this`, proves nothing: it may
|
|
1733
|
+
* read the message from `this` or a closure.
|
|
1734
|
+
*/
|
|
1735
|
+
private handlerReadsNothingFromItsParameters;
|
|
1736
|
+
/**
|
|
1737
|
+
* True unless `argument` is proven to be data that cannot receive anything
|
|
1738
|
+
* from the call it is handed to (carrick#827). Data is a primitive or literal,
|
|
1739
|
+
* `null`/`undefined`, or an object with no call or construct signature and
|
|
1740
|
+
* no callable member; an array of such, or a union all of whose members are.
|
|
1741
|
+
* A callback, an object with a method (a stream, an emitter), or a type the
|
|
1742
|
+
* compiler cannot state (`any`, `unknown`, a type parameter) can receive.
|
|
1743
|
+
* Sibling of `callResultIsPlainData`, which reads a call's result and so
|
|
1744
|
+
* rejects primitives and empty objects; an argument of either is data.
|
|
1745
|
+
*/
|
|
1746
|
+
private argumentCanReceive;
|
|
1632
1747
|
/**
|
|
1633
1748
|
* Scan a handler function body for the FIRST request-body read that carries a
|
|
1634
1749
|
* type, and return that type's structural text — or null when the body holds
|