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.
@@ -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