carrick 0.3.104 → 0.3.106
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/capture/anchors.d.ts +13 -0
- package/sidecar/dist/src/capture/anchors.js +229 -37
- package/sidecar/dist/src/capture/api.d.ts +64 -4
- package/sidecar/dist/src/capture/check-classify.d.ts +10 -1
- package/sidecar/dist/src/capture/check-classify.js +23 -12
- package/sidecar/dist/src/capture/check-fields.js +4 -6
- package/sidecar/dist/src/capture/check-probe.js +16 -6
- package/sidecar/dist/src/capture/check-scrub.d.ts +3 -0
- package/sidecar/dist/src/capture/check-scrub.js +8 -4
- package/sidecar/dist/src/capture/check.js +75 -49
- package/sidecar/dist/src/capture/deep-walk.d.ts +20 -0
- package/sidecar/dist/src/capture/deep-walk.js +51 -11
- package/sidecar/dist/src/capture/guarded-fs.d.ts +5 -1
- package/sidecar/dist/src/capture/guarded-fs.js +23 -1
- package/sidecar/dist/src/capture/index.js +14 -2
- package/sidecar/dist/src/capture/member-name.d.ts +20 -0
- package/sidecar/dist/src/capture/member-name.js +24 -0
- package/sidecar/dist/src/capture/self-check.js +50 -9
- package/sidecar/dist/src/capture/service-config.d.ts +2 -0
- package/sidecar/dist/src/capture/service-config.js +1 -1
- package/sidecar/dist/src/failure-path.d.ts +67 -0
- package/sidecar/dist/src/failure-path.js +236 -0
- package/sidecar/dist/src/printed-names.d.ts +43 -0
- package/sidecar/dist/src/printed-names.js +186 -0
- package/sidecar/dist/src/retype.js +60 -99
- package/sidecar/dist/src/type-inferrer.d.ts +35 -3
- package/sidecar/dist/src/type-inferrer.js +208 -13
- package/sidecar/dist/src/type-structural-expander.js +10 -1
- package/sidecar/dist/src/types.d.ts +24 -0
- package/sidecar/dist/src/validators.d.ts +76 -0
- package/sidecar/dist/src/validators.js +8 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* carrick#1836: what each bare name in an inference's printed text meant.
|
|
3
|
+
*
|
|
4
|
+
* The structural printer (`type-structural-expander.ts`) hands some subtrees
|
|
5
|
+
* back to the compiler's own print with no enclosing declaration, and that
|
|
6
|
+
* print writes every named type by its bare name: a database client's row,
|
|
7
|
+
* which is a library's mapped type, prints `{ status: EntityStatus; ... }`.
|
|
8
|
+
* The file a request names often never imports `EntityStatus`, so the text
|
|
9
|
+
* names nothing where it is read (TS2304), and the member reads `any` in the
|
|
10
|
+
* capture's surface.
|
|
11
|
+
*
|
|
12
|
+
* The compiler knew the symbol when it printed the name, and only then. So
|
|
13
|
+
* every type print made while an inference runs is noted (`notePrintedType`),
|
|
14
|
+
* and when it ends (`PrintedTypes.namesIn`) the names its text prints that the
|
|
15
|
+
* request's file cannot resolve are looked up in those prints: the node
|
|
16
|
+
* builder gives each name it writes the symbol it wrote it for. Each such
|
|
17
|
+
* symbol is recorded as the module that declares it and its export path
|
|
18
|
+
* there. A name printed for two declarations is recorded twice, and the
|
|
19
|
+
* reader of the record decides what an ambiguous name means.
|
|
20
|
+
*
|
|
21
|
+
* Only the prints whose text writes such a name are read back, and only when
|
|
22
|
+
* the inference's text has one, so an inference whose names all resolve costs
|
|
23
|
+
* no more than keeping the list.
|
|
24
|
+
*/
|
|
25
|
+
import { ts } from 'ts-morph';
|
|
26
|
+
/** The prints of the inference running now, when one is recording. */
|
|
27
|
+
let recording;
|
|
28
|
+
/**
|
|
29
|
+
* Note a type the compiler printed as `text`, so the inference running now can
|
|
30
|
+
* tell what the names in that print meant. Does nothing outside a recording.
|
|
31
|
+
*/
|
|
32
|
+
export function notePrintedType(type, enclosing, text) {
|
|
33
|
+
recording?.push({ type: type.compilerType, enclosing: enclosing?.compilerNode, text });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Same flags the printers pass `typeToString` (`NoTruncation`, `InTypeAlias`),
|
|
37
|
+
* so the node read back is the one that was printed.
|
|
38
|
+
*/
|
|
39
|
+
const NODE_FLAGS = ts.NodeBuilderFlags.NoTruncation | ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.IgnoreErrors;
|
|
40
|
+
const REFERENCE_MEANING = ts.SymbolFlags.Type | ts.SymbolFlags.Namespace | ts.SymbolFlags.Alias;
|
|
41
|
+
/** The type prints one inference made. */
|
|
42
|
+
export class PrintedTypes {
|
|
43
|
+
prints = [];
|
|
44
|
+
/** Run `read`, noting every type it prints. */
|
|
45
|
+
during(read) {
|
|
46
|
+
const outer = recording;
|
|
47
|
+
recording = this.prints;
|
|
48
|
+
try {
|
|
49
|
+
return read();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
recording = outer;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The declarations behind each name `texts` print that `source` does not
|
|
57
|
+
* resolve, sorted, one entry per distinct declaration. `checker` must be
|
|
58
|
+
* the one the prints were made with: call this before the program changes.
|
|
59
|
+
*/
|
|
60
|
+
namesIn(texts, source, checker) {
|
|
61
|
+
const wanted = new Set();
|
|
62
|
+
for (const text of texts) {
|
|
63
|
+
for (const name of referencedNames(text)) {
|
|
64
|
+
if (!checker.resolveName(name, source, REFERENCE_MEANING, false))
|
|
65
|
+
wanted.add(name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (wanted.size === 0)
|
|
69
|
+
return [];
|
|
70
|
+
// Only a print whose text writes one of the names can say what it meant.
|
|
71
|
+
const alternatives = [...wanted].map((name) => name.replace(/\$/g, '\\$')).join('|');
|
|
72
|
+
const writes = new RegExp(`(?<![\\w$.])(?:${alternatives})(?![\\w$])`);
|
|
73
|
+
const found = new Map();
|
|
74
|
+
const identities = new Map();
|
|
75
|
+
const visited = new Map();
|
|
76
|
+
for (const { type, enclosing, text } of this.prints) {
|
|
77
|
+
if (!writes.test(text))
|
|
78
|
+
continue;
|
|
79
|
+
const at = visited.get(type) ?? new Set();
|
|
80
|
+
if (at.has(enclosing))
|
|
81
|
+
continue;
|
|
82
|
+
visited.set(type, at.add(enclosing));
|
|
83
|
+
let node;
|
|
84
|
+
try {
|
|
85
|
+
node = checker.typeToTypeNode(type, enclosing, NODE_FLAGS);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const visit = (current) => {
|
|
91
|
+
if (ts.isTypeReferenceNode(current)) {
|
|
92
|
+
const id = leftmost(current.typeName);
|
|
93
|
+
const name = ts.idText(id);
|
|
94
|
+
const symbol = wanted.has(name) ? symbolOf(id, checker) : undefined;
|
|
95
|
+
if (symbol) {
|
|
96
|
+
if (!identities.has(symbol))
|
|
97
|
+
identities.set(symbol, identityOf(symbol, checker));
|
|
98
|
+
const identity = identities.get(symbol);
|
|
99
|
+
if (identity) {
|
|
100
|
+
found.set(`${name}\0${identity.file}\0${identity.export_path.join('.')}`, { name, ...identity });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
ts.forEachChild(current, visit);
|
|
105
|
+
};
|
|
106
|
+
if (node)
|
|
107
|
+
visit(node);
|
|
108
|
+
}
|
|
109
|
+
return [...found.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([, entry]) => entry);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function leftmost(name) {
|
|
113
|
+
return ts.isIdentifier(name) ? name : leftmost(name.left);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The leftmost names of the type references `text` makes, less the type
|
|
117
|
+
* parameters the text declares itself (`[K in ...]`, `infer U`).
|
|
118
|
+
*/
|
|
119
|
+
function referencedNames(text) {
|
|
120
|
+
const parsed = ts.createSourceFile('printed.ts', `type __Printed = ${text};`, ts.ScriptTarget.Latest, true);
|
|
121
|
+
const names = new Set();
|
|
122
|
+
const declared = new Set();
|
|
123
|
+
const visit = (node) => {
|
|
124
|
+
if (ts.isTypeParameterDeclaration(node))
|
|
125
|
+
declared.add(node.name.text);
|
|
126
|
+
if (ts.isTypeReferenceNode(node))
|
|
127
|
+
names.add(ts.idText(leftmost(node.typeName)));
|
|
128
|
+
ts.forEachChild(node, visit);
|
|
129
|
+
};
|
|
130
|
+
visit(parsed);
|
|
131
|
+
for (const name of declared)
|
|
132
|
+
names.delete(name);
|
|
133
|
+
names.delete('__Printed');
|
|
134
|
+
return names;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The symbol a printed name was written for. The node builder sets it on
|
|
138
|
+
* every identifier it creates (`symbol` is not in the public typings); a name
|
|
139
|
+
* it copied from source carries none, and is read at its original node.
|
|
140
|
+
*/
|
|
141
|
+
function symbolOf(id, checker) {
|
|
142
|
+
const written = id.symbol;
|
|
143
|
+
if (written)
|
|
144
|
+
return written;
|
|
145
|
+
const original = ts.getOriginalNode(id);
|
|
146
|
+
return original !== id && original.parent ? checker.getSymbolAtLocation(original) : undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The module that declares `symbol`'s type and the export path to it there,
|
|
150
|
+
* or undefined for a type no module export reaches: a global, a type
|
|
151
|
+
* parameter, a declaration a function body holds, a module itself.
|
|
152
|
+
*/
|
|
153
|
+
function identityOf(symbol, checker) {
|
|
154
|
+
const resolve = (s) => s.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(s) : s;
|
|
155
|
+
const target = resolve(symbol);
|
|
156
|
+
if (!(target.flags & (ts.SymbolFlags.Type | ts.SymbolFlags.Namespace)) ||
|
|
157
|
+
target.flags & ts.SymbolFlags.TypeParameter) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const declaration = target.declarations?.[0];
|
|
161
|
+
if (!declaration || ts.isSourceFile(declaration))
|
|
162
|
+
return undefined;
|
|
163
|
+
const file = declaration.getSourceFile();
|
|
164
|
+
const moduleSymbol = checker.getSymbolAtLocation(file);
|
|
165
|
+
if (!moduleSymbol)
|
|
166
|
+
return undefined;
|
|
167
|
+
const direct = checker.getExportsOfModule(moduleSymbol).filter((e) => resolve(e) === target);
|
|
168
|
+
if (direct.length > 0) {
|
|
169
|
+
const chosen = direct.find((e) => e.getName() === target.getName()) ?? direct[0];
|
|
170
|
+
return { file: file.fileName, export_path: [chosen.getName()] };
|
|
171
|
+
}
|
|
172
|
+
// A namespace member: `"<module>".Billing.Kind`, checked step by step.
|
|
173
|
+
const qualified = checker.getFullyQualifiedName(target);
|
|
174
|
+
const close = qualified.startsWith('"') ? qualified.indexOf('"', 1) : -1;
|
|
175
|
+
if (close < 0)
|
|
176
|
+
return undefined;
|
|
177
|
+
const exportPath = qualified.slice(close + 2).split('.');
|
|
178
|
+
let current = moduleSymbol;
|
|
179
|
+
for (const part of exportPath) {
|
|
180
|
+
const next = current
|
|
181
|
+
? checker.getExportsOfModule(current).find((e) => e.getName() === part)
|
|
182
|
+
: undefined;
|
|
183
|
+
current = next && resolve(next);
|
|
184
|
+
}
|
|
185
|
+
return current === target ? { file: file.fileName, export_path: exportPath } : undefined;
|
|
186
|
+
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* project other requests read is the project the scan loaded.
|
|
26
26
|
*/
|
|
27
27
|
import { Node, SyntaxKind, ts } from 'ts-morph';
|
|
28
|
+
import { readsResponseStatus, statusesIn, SUCCEEDED, testsOnPath, } from './failure-path.js';
|
|
28
29
|
import { fileDiagnostics } from './unwidened.js';
|
|
29
30
|
/** Names appended to a file that is not ours carry this prefix. */
|
|
30
31
|
const PREFIX = '__carrick_';
|
|
@@ -688,111 +689,71 @@ function responseTest(read) {
|
|
|
688
689
|
return (node) => Node.isIdentifier(node) && node.getSymbol() === symbol;
|
|
689
690
|
}
|
|
690
691
|
/**
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
692
|
+
* Whether `node` sits on the response's error path (`'failure'`), on a path
|
|
693
|
+
* whose status tests do not say (`'unclear'`), or where the producer's body
|
|
694
|
+
* is read (`undefined`, also when no test of the response is on its path).
|
|
695
|
+
*
|
|
696
|
+
* The tests on the path are read as failure-path.ts reads them, each as the
|
|
697
|
+
* statuses that take the side `node` is on, and taken together. On top of
|
|
698
|
+
* that reading, in this order:
|
|
699
|
+
*
|
|
700
|
+
* - no success status reaches `node`: the error path;
|
|
701
|
+
* - a test lets through every status but one success status that carries a
|
|
702
|
+
* body (the side of `res.status === 200` the read after an early return on
|
|
703
|
+
* it is on): the error path. This is the consumer's own reading of that
|
|
704
|
+
* test, kept even when only success statuses get that far, because the
|
|
705
|
+
* read there is not the body the source singled out;
|
|
706
|
+
* - no status from 400 up reaches `node`: the producer's body;
|
|
707
|
+
* - a `switch` on the status, or a test the reading cannot follow exactly
|
|
708
|
+
* (`res.status === OK`, `res.ok || retry`), is on the path: unclear;
|
|
709
|
+
* - the tests take away a success status that carries a body
|
|
710
|
+
* (`res.status === 200 || res.status === 404`, `res.status > 200`):
|
|
711
|
+
* unclear;
|
|
712
|
+
* - otherwise they took away only error statuses, or statuses that carry no
|
|
713
|
+
* content (carrick#1813: the retype only runs against a producer that
|
|
714
|
+
* publishes a body, and that body never arrives with a 204 or 205), and
|
|
715
|
+
* `node` is where the body is read, as it is with no test at all.
|
|
694
716
|
*/
|
|
695
717
|
function sideOf(node, isResponse) {
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
if (
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (statement === child)
|
|
721
|
-
break;
|
|
722
|
-
if (Node.isIfStatement(statement) &&
|
|
723
|
-
!statement.getElseStatement() &&
|
|
724
|
-
exits(statement.getThenStatement())) {
|
|
725
|
-
note(branchSide(statement.getExpression(), false, isResponse));
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
return found;
|
|
718
|
+
const sides = testsOnPath(node, undefined, isResponse);
|
|
719
|
+
const switched = node.getAncestors().some((ancestor) => {
|
|
720
|
+
if (!Node.isCaseClause(ancestor) && !Node.isDefaultClause(ancestor))
|
|
721
|
+
return false;
|
|
722
|
+
const statement = ancestor.getParent()?.getParent();
|
|
723
|
+
return (!!statement &&
|
|
724
|
+
Node.isSwitchStatement(statement) &&
|
|
725
|
+
readsResponseStatus(statement.getExpression(), isResponse));
|
|
726
|
+
});
|
|
727
|
+
if (sides.length === 0 && !switched)
|
|
728
|
+
return undefined;
|
|
729
|
+
const admitted = (status) => sides.every((side) => side.admits(status));
|
|
730
|
+
const reaching = statusesIn(admitted);
|
|
731
|
+
if (!reaching.some(SUCCEEDED))
|
|
732
|
+
return 'failure';
|
|
733
|
+
if (sides.some((side) => singlesOutABody(side.admits)))
|
|
734
|
+
return 'failure';
|
|
735
|
+
if (!reaching.some((status) => status >= 400))
|
|
736
|
+
return undefined;
|
|
737
|
+
if (switched || sides.some((side) => side.inexact))
|
|
738
|
+
return 'unclear';
|
|
739
|
+
if (statusesIn((status) => !admitted(status)).some(carriesBody))
|
|
740
|
+
return 'unclear';
|
|
741
|
+
return undefined;
|
|
731
742
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
743
|
+
/** `statuses` is every status but one success status that carries a body. */
|
|
744
|
+
function singlesOutABody(statuses) {
|
|
745
|
+
const excluded = statusesIn((status) => !statuses(status));
|
|
746
|
+
return excluded.length === 1 && carriesBody(excluded[0]);
|
|
747
|
+
}
|
|
748
|
+
/** A success status whose response can carry the producer's body. */
|
|
749
|
+
function carriesBody(status) {
|
|
750
|
+
return SUCCEEDED(status) && !NO_CONTENT_STATUSES.has(status);
|
|
737
751
|
}
|
|
738
752
|
/**
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
* negations. Any other test of the response is `'unclear'`; a condition that does not
|
|
742
|
-
* test the response is `undefined`.
|
|
753
|
+
* The success statuses whose response carries no content (RFC 9110: 204 No
|
|
754
|
+
* Content, 205 Reset Content).
|
|
743
755
|
*/
|
|
744
|
-
|
|
745
|
-
let e = condition;
|
|
746
|
-
while (Node.isParenthesizedExpression(e))
|
|
747
|
-
e = e.getExpression();
|
|
748
|
-
if (Node.isPrefixUnaryExpression(e) && e.getOperatorToken() === SyntaxKind.ExclamationToken) {
|
|
749
|
-
const inner = okWhenTrue(e.getOperand(), isResponse);
|
|
750
|
-
return typeof inner === 'boolean' ? !inner : inner;
|
|
751
|
-
}
|
|
752
|
-
if (isMember(e, 'ok', isResponse))
|
|
753
|
-
return true;
|
|
754
|
-
if (Node.isBinaryExpression(e)) {
|
|
755
|
-
const op = e.getOperatorToken().getKind();
|
|
756
|
-
const [left, right] = [e.getLeft(), e.getRight()];
|
|
757
|
-
const value = isMember(left, 'status', isResponse) && Node.isNumericLiteral(right)
|
|
758
|
-
? right.getLiteralValue()
|
|
759
|
-
: undefined;
|
|
760
|
-
if (value !== undefined) {
|
|
761
|
-
const success = value >= 200 && value < 300;
|
|
762
|
-
switch (op) {
|
|
763
|
-
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
764
|
-
case SyntaxKind.EqualsEqualsToken:
|
|
765
|
-
return success;
|
|
766
|
-
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
767
|
-
case SyntaxKind.ExclamationEqualsToken:
|
|
768
|
-
return !success;
|
|
769
|
-
case SyntaxKind.GreaterThanEqualsToken:
|
|
770
|
-
if (value >= 300)
|
|
771
|
-
return false;
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
return 'unclear';
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
return testsResponse(e, isResponse) ? 'unclear' : undefined;
|
|
778
|
-
}
|
|
779
|
-
function isMember(node, name, isResponse) {
|
|
780
|
-
return (Node.isPropertyAccessExpression(node) && node.getName() === name && isResponse(node.getExpression()));
|
|
781
|
-
}
|
|
782
|
-
/** The expression reads the response's `ok` or `status`. */
|
|
783
|
-
function testsResponse(node, isResponse) {
|
|
784
|
-
return [node, ...node.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)].some((n) => isMember(n, 'ok', isResponse) || isMember(n, 'status', isResponse));
|
|
785
|
-
}
|
|
786
|
-
/** A statement that always leaves the function. */
|
|
787
|
-
function exits(statement) {
|
|
788
|
-
if (Node.isReturnStatement(statement) || Node.isThrowStatement(statement))
|
|
789
|
-
return true;
|
|
790
|
-
if (Node.isBlock(statement)) {
|
|
791
|
-
const last = statement.getStatements().at(-1);
|
|
792
|
-
return !!last && exits(last);
|
|
793
|
-
}
|
|
794
|
-
return false;
|
|
795
|
-
}
|
|
756
|
+
const NO_CONTENT_STATUSES = new Set([204, 205]);
|
|
796
757
|
/** `node.json()` with no arguments, where `node` is the receiver. */
|
|
797
758
|
function bodyReadOn(node) {
|
|
798
759
|
const access = node.getParent();
|
|
@@ -97,6 +97,11 @@ export declare class TypeInferrer {
|
|
|
97
97
|
* @returns InferResult with inferred types or errors
|
|
98
98
|
*/
|
|
99
99
|
infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig): InferResult;
|
|
100
|
+
/**
|
|
101
|
+
* carrick#1836: list on `result` the declarations behind the names its text
|
|
102
|
+
* prints that the request's file cannot resolve (`PrintedTypes.namesIn`).
|
|
103
|
+
*/
|
|
104
|
+
private recordPrintedNames;
|
|
100
105
|
/**
|
|
101
106
|
* carrick#1516: read each response inference again with the literals on its
|
|
102
107
|
* handler's path marked `as const`, and record the narrower type the handler
|
|
@@ -257,6 +262,25 @@ export declare class TypeInferrer {
|
|
|
257
262
|
*/
|
|
258
263
|
private receivingCallOf;
|
|
259
264
|
private inferCallResult;
|
|
265
|
+
/**
|
|
266
|
+
* The terminal of a call's def-use walk is a zero-argument `.text()` read of
|
|
267
|
+
* the call's own result (carrick#1842): on its binding (`res.text()`,
|
|
268
|
+
* through a declaration, `await` or a cast), or in the callback a `then` on
|
|
269
|
+
* the call hands the response to (`fetch(u).then((res) => res.text())`).
|
|
270
|
+
*/
|
|
271
|
+
private textReadAtTerminal;
|
|
272
|
+
/** `call` is `<receiver>.text()` with no arguments, and `accept` takes its receiver. */
|
|
273
|
+
private isTextReadOf;
|
|
274
|
+
/**
|
|
275
|
+
* The call's resolved signature, at this site, types a member of an object
|
|
276
|
+
* argument as exactly the literal `'text'`, and the source passes that
|
|
277
|
+
* literal there (carrick#1842). That is how a request library lets a caller
|
|
278
|
+
* choose a text body from the formats it offers (`{ type: 'text' }`): a
|
|
279
|
+
* generic config instantiated by the literal, or an overload taken by it.
|
|
280
|
+
* A member typed as a wider union (`kind: 'text' | 'image'`) chooses no
|
|
281
|
+
* format, and no member name is read.
|
|
282
|
+
*/
|
|
283
|
+
private callChoosesTextBody;
|
|
260
284
|
/**
|
|
261
285
|
* What the source states the body read at `terminal` to be, when the type
|
|
262
286
|
* `extractExplicitTypeFromAncestor` printed for it is stated AT the read
|
|
@@ -349,10 +373,10 @@ export declare class TypeInferrer {
|
|
|
349
373
|
* `query.data`, `envelope` in `envelope.list[0]` — or `undefined` when the
|
|
350
374
|
* identifier names the value itself.
|
|
351
375
|
*
|
|
352
|
-
* A member CALL is not a projection: `res.
|
|
376
|
+
* A member CALL is not a projection: `res.blob()` yields a body rather than
|
|
353
377
|
* a part of one, and what it returns stays the walk's business. The
|
|
354
|
-
* zero-argument json body
|
|
355
|
-
* is asked.
|
|
378
|
+
* zero-argument json and text body reads have their own branch and are
|
|
379
|
+
* taken before this is asked.
|
|
356
380
|
*/
|
|
357
381
|
private projectionOnReceiver;
|
|
358
382
|
/**
|
|
@@ -431,9 +455,17 @@ export declare class TypeInferrer {
|
|
|
431
455
|
* about the body and is skipped.
|
|
432
456
|
*/
|
|
433
457
|
private castsOfUnreadParameter;
|
|
458
|
+
/**
|
|
459
|
+
* The zero-argument whole-body read that takes `identifier` as its receiver,
|
|
460
|
+
* `res.json()` or `res.text()`, or `undefined`. A text read is a body read
|
|
461
|
+
* like a json one (carrick#1842): without it, `return res.text()` left the
|
|
462
|
+
* walk on the response binding and published the transport object.
|
|
463
|
+
*/
|
|
434
464
|
private bodyReadOnReceiver;
|
|
435
465
|
private collectDefUseNodes;
|
|
436
466
|
private expressionUsesNames;
|
|
467
|
+
/** `expr` is a use of a tracked name, or contains one. */
|
|
468
|
+
private usesTrackedNames;
|
|
437
469
|
private isIdentifierUsage;
|
|
438
470
|
private isInFunctionScope;
|
|
439
471
|
/**
|