carrick 0.3.106 → 0.3.107
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/dist/hook/refresh.js +8 -1
- package/dist/hook/refresh.js.map +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.d.ts +15 -2
- package/sidecar/dist/src/capture/anchors.js +28 -11
- package/sidecar/dist/src/capture/check-poison.js +4 -14
- package/sidecar/dist/src/capture/index.js +54 -36
- package/sidecar/dist/src/capture/installed-package.d.ts +2 -0
- package/sidecar/dist/src/capture/installed-package.js +2 -1
- package/sidecar/dist/src/capture/outside-root.d.ts +19 -0
- package/sidecar/dist/src/capture/outside-root.js +39 -2
- package/sidecar/dist/src/capture/self-check.js +3 -13
- package/sidecar/dist/src/capture/specifiers.d.ts +14 -0
- package/sidecar/dist/src/capture/specifiers.js +25 -0
- package/sidecar/dist/src/function-line-index.d.ts +30 -0
- package/sidecar/dist/src/function-line-index.js +162 -0
- package/sidecar/dist/src/index.d.ts +5 -0
- package/sidecar/dist/src/index.js +29 -5
- package/sidecar/dist/src/line-index.d.ts +9 -0
- package/sidecar/dist/src/line-index.js +26 -0
- package/sidecar/dist/src/progress.d.ts +22 -0
- package/sidecar/dist/src/progress.js +31 -0
- package/sidecar/dist/src/retype.js +1 -19
- package/sidecar/dist/src/type-inferrer.d.ts +92 -11
- package/sidecar/dist/src/type-inferrer.js +356 -149
- package/sidecar/dist/src/types.d.ts +13 -6
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The function a line names, from an index built once per source file
|
|
3
|
+
* (carrick#1915).
|
|
4
|
+
*
|
|
5
|
+
* A signature request carries only the line its function starts on, and a
|
|
6
|
+
* signature pass sends one request per unannotated slot: thousands of
|
|
7
|
+
* lookups, several per function and many per file. Answering each by walking
|
|
8
|
+
* the file's every node cost the file's size per slot, and was 81% of the
|
|
9
|
+
* pass on a 2,345-file program. Here the file is walked once, over the
|
|
10
|
+
* compiler's own nodes, and each lookup reads the few entries near its line.
|
|
11
|
+
*
|
|
12
|
+
* The index belongs to the compiler's source file node, not to the file's
|
|
13
|
+
* name: replacing a file's text gives it a new node, so a file rewritten for
|
|
14
|
+
* one reading (the unwidened reading, the retype check) and then restored is
|
|
15
|
+
* indexed again each time, and never answered from positions it no longer has.
|
|
16
|
+
*/
|
|
17
|
+
import { SyntaxKind, ts, } from 'ts-morph';
|
|
18
|
+
import { lineIndex } from './line-index.js';
|
|
19
|
+
/** How far, in lines, a function may start from the line that names it. */
|
|
20
|
+
const LINE_TOLERANCE = 2;
|
|
21
|
+
const FUNCTION_KINDS = new Set([
|
|
22
|
+
SyntaxKind.FunctionDeclaration,
|
|
23
|
+
SyntaxKind.ArrowFunction,
|
|
24
|
+
SyntaxKind.FunctionExpression,
|
|
25
|
+
SyntaxKind.MethodDeclaration,
|
|
26
|
+
]);
|
|
27
|
+
/** The kinds ts-morph's `Node.isStatement` accepts. */
|
|
28
|
+
const STATEMENT_KINDS = new Set([
|
|
29
|
+
SyntaxKind.Block,
|
|
30
|
+
SyntaxKind.BreakStatement,
|
|
31
|
+
SyntaxKind.ClassDeclaration,
|
|
32
|
+
SyntaxKind.ContinueStatement,
|
|
33
|
+
SyntaxKind.DebuggerStatement,
|
|
34
|
+
SyntaxKind.DoStatement,
|
|
35
|
+
SyntaxKind.EmptyStatement,
|
|
36
|
+
SyntaxKind.EnumDeclaration,
|
|
37
|
+
SyntaxKind.ExportAssignment,
|
|
38
|
+
SyntaxKind.ExportDeclaration,
|
|
39
|
+
SyntaxKind.ExpressionStatement,
|
|
40
|
+
SyntaxKind.ForInStatement,
|
|
41
|
+
SyntaxKind.ForOfStatement,
|
|
42
|
+
SyntaxKind.ForStatement,
|
|
43
|
+
SyntaxKind.FunctionDeclaration,
|
|
44
|
+
SyntaxKind.IfStatement,
|
|
45
|
+
SyntaxKind.ImportDeclaration,
|
|
46
|
+
SyntaxKind.ImportEqualsDeclaration,
|
|
47
|
+
SyntaxKind.InterfaceDeclaration,
|
|
48
|
+
SyntaxKind.LabeledStatement,
|
|
49
|
+
SyntaxKind.ModuleBlock,
|
|
50
|
+
SyntaxKind.ModuleDeclaration,
|
|
51
|
+
SyntaxKind.NotEmittedStatement,
|
|
52
|
+
SyntaxKind.ReturnStatement,
|
|
53
|
+
SyntaxKind.SwitchStatement,
|
|
54
|
+
SyntaxKind.ThrowStatement,
|
|
55
|
+
SyntaxKind.TryStatement,
|
|
56
|
+
SyntaxKind.TypeAliasDeclaration,
|
|
57
|
+
SyntaxKind.VariableStatement,
|
|
58
|
+
SyntaxKind.WhileStatement,
|
|
59
|
+
SyntaxKind.WithStatement,
|
|
60
|
+
]);
|
|
61
|
+
const indexes = new WeakMap();
|
|
62
|
+
/**
|
|
63
|
+
* The function whose declaration starts at, or within `LINE_TOLERANCE` lines
|
|
64
|
+
* of, the given line: the closest, then the smallest, then the first in the
|
|
65
|
+
* file. A function starting after the line is passed over when a statement
|
|
66
|
+
* that opens on the line or after it, on a line before the function's, does
|
|
67
|
+
* not contain the function. Undefined when no function is left.
|
|
68
|
+
*
|
|
69
|
+
* These are `TypeInferrer.findFunctionByLine`'s rules, which says why each is
|
|
70
|
+
* there; this is where they are computed.
|
|
71
|
+
*/
|
|
72
|
+
export function functionAtLine(sourceFile, line) {
|
|
73
|
+
if (!Number.isFinite(line))
|
|
74
|
+
return undefined;
|
|
75
|
+
const index = indexOf(sourceFile.compilerNode);
|
|
76
|
+
/** Statements opening inside the forward window. */
|
|
77
|
+
const windowStatements = [];
|
|
78
|
+
for (let at = Math.ceil(line); at <= line + LINE_TOLERANCE; at++) {
|
|
79
|
+
const opening = index.statementsByLine.get(at);
|
|
80
|
+
if (opening)
|
|
81
|
+
windowStatements.push(...opening);
|
|
82
|
+
}
|
|
83
|
+
const separatedFromAnchor = (fn) => windowStatements.some((statement) => statement.line < fn.line && !(statement.start <= fn.start && statement.end >= fn.end));
|
|
84
|
+
let best;
|
|
85
|
+
for (let at = Math.ceil(line - LINE_TOLERANCE); at <= line + LINE_TOLERANCE; at++) {
|
|
86
|
+
for (const fn of index.functionsByLine.get(at) ?? []) {
|
|
87
|
+
if (fn.line > line && separatedFromAnchor(fn))
|
|
88
|
+
continue;
|
|
89
|
+
if (best === undefined || isBetter(fn, best, line))
|
|
90
|
+
best = fn;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return best ? wrapped(sourceFile, best) : undefined;
|
|
94
|
+
}
|
|
95
|
+
/** Closer to the line, then smaller, then earlier in the file. */
|
|
96
|
+
function isBetter(fn, best, line) {
|
|
97
|
+
const delta = Math.abs(fn.line - line);
|
|
98
|
+
const bestDelta = Math.abs(best.line - line);
|
|
99
|
+
if (delta !== bestDelta)
|
|
100
|
+
return delta < bestDelta;
|
|
101
|
+
const size = fn.end - fn.start;
|
|
102
|
+
const bestSize = best.end - best.start;
|
|
103
|
+
if (size !== bestSize)
|
|
104
|
+
return size < bestSize;
|
|
105
|
+
return fn.order < best.order;
|
|
106
|
+
}
|
|
107
|
+
function indexOf(file) {
|
|
108
|
+
let index = indexes.get(file);
|
|
109
|
+
if (!index) {
|
|
110
|
+
index = buildIndex(file);
|
|
111
|
+
indexes.set(file, index);
|
|
112
|
+
}
|
|
113
|
+
return index;
|
|
114
|
+
}
|
|
115
|
+
function buildIndex(file) {
|
|
116
|
+
// Lines as ts-morph's `getStartLineNumber` counts them, which is what the
|
|
117
|
+
// scanner's line numbers were matched against.
|
|
118
|
+
const lineAt = lineIndex(file.text);
|
|
119
|
+
const functionsByLine = new Map();
|
|
120
|
+
const statementsByLine = new Map();
|
|
121
|
+
let order = 0;
|
|
122
|
+
const visit = (node) => {
|
|
123
|
+
const isFunction = FUNCTION_KINDS.has(node.kind);
|
|
124
|
+
const isStatement = STATEMENT_KINDS.has(node.kind);
|
|
125
|
+
if (isFunction || isStatement) {
|
|
126
|
+
const start = node.getStart(file);
|
|
127
|
+
const extent = { start, end: node.end, line: lineAt(start) };
|
|
128
|
+
if (isFunction) {
|
|
129
|
+
pushAt(functionsByLine, extent.line, { ...extent, node, order: order++ });
|
|
130
|
+
}
|
|
131
|
+
if (isStatement) {
|
|
132
|
+
pushAt(statementsByLine, extent.line, extent);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
ts.forEachChild(node, visit);
|
|
136
|
+
};
|
|
137
|
+
ts.forEachChild(file, visit);
|
|
138
|
+
return { functionsByLine, statementsByLine };
|
|
139
|
+
}
|
|
140
|
+
function pushAt(byLine, line, entry) {
|
|
141
|
+
const entries = byLine.get(line);
|
|
142
|
+
if (entries)
|
|
143
|
+
entries.push(entry);
|
|
144
|
+
else
|
|
145
|
+
byLine.set(line, [entry]);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The ts-morph node for an indexed function. Only this one is wrapped: the
|
|
149
|
+
* deepest node at the function's first character is the function or sits
|
|
150
|
+
* inside it, so its ancestors lead there, and the rest of the file's nodes are
|
|
151
|
+
* never given wrappers they would keep for the life of the project.
|
|
152
|
+
*/
|
|
153
|
+
function wrapped(sourceFile, fn) {
|
|
154
|
+
let node = sourceFile.getDescendantAtPos(fn.start);
|
|
155
|
+
while (node && node.compilerNode !== fn.node) {
|
|
156
|
+
node = node.getParent();
|
|
157
|
+
}
|
|
158
|
+
// An invariant, not a case: no shape is known where the climb misses. If
|
|
159
|
+
// one exists, the answer is still the function the index chose.
|
|
160
|
+
node ??= sourceFile.getFirstDescendant((descendant) => descendant.compilerNode === fn.node);
|
|
161
|
+
return node;
|
|
162
|
+
}
|
|
@@ -10,5 +10,10 @@
|
|
|
10
10
|
* - stdout is ONLY for JSON responses
|
|
11
11
|
* - stderr is for logging
|
|
12
12
|
* - Process stays alive between requests (warm standby)
|
|
13
|
+
* - One request at a time. A handler that runs the compiler blocks the event
|
|
14
|
+
* loop until it returns, so a request sent meanwhile waits in the pipe, and
|
|
15
|
+
* nothing can cancel the one that is running. The scanner kills a process
|
|
16
|
+
* that goes silent past its deadline; a long handler stays alive by writing
|
|
17
|
+
* `progress` frames as it finishes units of work (writeProgress).
|
|
13
18
|
*/
|
|
14
19
|
export {};
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* - stdout is ONLY for JSON responses
|
|
11
11
|
* - stderr is for logging
|
|
12
12
|
* - Process stays alive between requests (warm standby)
|
|
13
|
+
* - One request at a time. A handler that runs the compiler blocks the event
|
|
14
|
+
* loop until it returns, so a request sent meanwhile waits in the pipe, and
|
|
15
|
+
* nothing can cancel the one that is running. The scanner kills a process
|
|
16
|
+
* that goes silent past its deadline; a long handler stays alive by writing
|
|
17
|
+
* `progress` frames as it finishes units of work (writeProgress).
|
|
13
18
|
*/
|
|
14
19
|
import * as path from 'node:path';
|
|
15
20
|
import * as readline from 'node:readline';
|
|
@@ -21,6 +26,7 @@ import { DefinitionResolver } from './definition-resolver.js';
|
|
|
21
26
|
import { captureStub, findDisqualifyingTopTypes, jsonWireDeclarations, runCheck, } from './capture/index.js';
|
|
22
27
|
import { Retyper } from './retype.js';
|
|
23
28
|
import { LibraryClaimsVerifier, httpCheck } from './library-claims.js';
|
|
29
|
+
import { PROGRESS_INTERVAL_MS, atMostEvery } from './progress.js';
|
|
24
30
|
// ===========================================================================
|
|
25
31
|
// Module-level state
|
|
26
32
|
// ===========================================================================
|
|
@@ -168,7 +174,11 @@ function handleBundle(request) {
|
|
|
168
174
|
log(`Bundling ${request.symbols.length} symbol(s)`);
|
|
169
175
|
// A symbol named by a path is bundled from the program of the project
|
|
170
176
|
// that owns that file (carrick#1604).
|
|
171
|
-
const results = [...byProject(request.symbols, (symbol) => symbol.source_file)].map(([key, symbols]) =>
|
|
177
|
+
const results = [...byProject(request.symbols, (symbol) => symbol.source_file)].map(([key, symbols]) => {
|
|
178
|
+
const { typeBundler } = projectComponents(key);
|
|
179
|
+
writeProgress(request.request_id, 'bundle', 'program ready');
|
|
180
|
+
return typeBundler.bundle(symbols);
|
|
181
|
+
});
|
|
172
182
|
const result = results.length === 1 ? results[0] : mergeBundles(results);
|
|
173
183
|
if (!result.success) {
|
|
174
184
|
return {
|
|
@@ -284,7 +294,13 @@ async function handleCheckV2Async(request) {
|
|
|
284
294
|
function handleInfer(request) {
|
|
285
295
|
try {
|
|
286
296
|
log(`Inferring ${request.requests.length} type(s)`);
|
|
287
|
-
|
|
297
|
+
// One count for the request, whichever programs its items span.
|
|
298
|
+
let done = 0;
|
|
299
|
+
const report = atMostEvery(PROGRESS_INTERVAL_MS, () => writeProgress(request.request_id, 'infer', `${done} of ${request.requests.length}`));
|
|
300
|
+
const results = [...byProject(request.requests, (item) => item.file_path)].map(([key, items]) => projectComponents(key).typeInferrer.infer(items, request.extraction_config, () => {
|
|
301
|
+
done += 1;
|
|
302
|
+
report();
|
|
303
|
+
}));
|
|
288
304
|
const result = results.length === 1
|
|
289
305
|
? results[0]
|
|
290
306
|
: (() => {
|
|
@@ -388,6 +404,8 @@ function handleVerifyLibraryClaims(request) {
|
|
|
388
404
|
const started = performance.now();
|
|
389
405
|
try {
|
|
390
406
|
const { claimsVerifier } = projectComponents();
|
|
407
|
+
// The verifier's budget starts now, and so does the scanner's wait.
|
|
408
|
+
writeProgress(request.request_id, 'verify_library_claims', 'program ready');
|
|
391
409
|
const fromDir = path.resolve(projectLoader.getRepoRoot(), request.from_dir);
|
|
392
410
|
log(`Verifying ${request.checks.length} library claim(s) from ${fromDir}`);
|
|
393
411
|
const { semantics, modules } = claimsVerifier.run(fromDir, request.checks, request.budget_ms ?? SEMANTICS_BUDGET_MS);
|
|
@@ -530,9 +548,15 @@ function writeResponse(response) {
|
|
|
530
548
|
process.stdout.write(json + '\n');
|
|
531
549
|
}
|
|
532
550
|
/**
|
|
533
|
-
* Write a non-terminal progress
|
|
534
|
-
*
|
|
535
|
-
*
|
|
551
|
+
* Write a non-terminal progress frame. Distinct `status: 'progress'` so
|
|
552
|
+
* clients skip it and wait for the terminal success/error frame; the scanner
|
|
553
|
+
* restarts its deadline for the request on each one (carrick#1914).
|
|
554
|
+
*
|
|
555
|
+
* A synchronous handler calls this between units of its work, with the event
|
|
556
|
+
* loop blocked. The frame still leaves at once: a write this small to a pipe
|
|
557
|
+
* with room in it completes inside the call, and the reader on the other end
|
|
558
|
+
* drains the pipe on its own thread, so there is room. It is written through
|
|
559
|
+
* the same stream as every answer, so a frame can never land inside one.
|
|
536
560
|
*/
|
|
537
561
|
function writeProgress(requestId, phase, message) {
|
|
538
562
|
process.stdout.write(JSON.stringify({ request_id: requestId, status: 'progress', phase, message }) + '\n');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 1-based line of a position in a text, counted by line feeds alone: one
|
|
3
|
+
* more than the `\n`s before the position. That is the count ts-morph's
|
|
4
|
+
* `getStartLineNumber` makes. A lone carriage return or a Unicode line
|
|
5
|
+
* separator ends a line for the compiler and not here.
|
|
6
|
+
*
|
|
7
|
+
* The text is read once; each lookup after that is a binary search.
|
|
8
|
+
*/
|
|
9
|
+
export declare function lineIndex(text: string): (pos: number) => number;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 1-based line of a position in a text, counted by line feeds alone: one
|
|
3
|
+
* more than the `\n`s before the position. That is the count ts-morph's
|
|
4
|
+
* `getStartLineNumber` makes. A lone carriage return or a Unicode line
|
|
5
|
+
* separator ends a line for the compiler and not here.
|
|
6
|
+
*
|
|
7
|
+
* The text is read once; each lookup after that is a binary search.
|
|
8
|
+
*/
|
|
9
|
+
export function lineIndex(text) {
|
|
10
|
+
const starts = [0];
|
|
11
|
+
for (let i = 0; i < text.length; i++)
|
|
12
|
+
if (text[i] === '\n')
|
|
13
|
+
starts.push(i + 1);
|
|
14
|
+
return (pos) => {
|
|
15
|
+
let lo = 0;
|
|
16
|
+
let hi = starts.length - 1;
|
|
17
|
+
while (lo < hi) {
|
|
18
|
+
const mid = (lo + hi + 1) >> 1;
|
|
19
|
+
if (starts[mid] <= pos)
|
|
20
|
+
lo = mid;
|
|
21
|
+
else
|
|
22
|
+
hi = mid - 1;
|
|
23
|
+
}
|
|
24
|
+
return lo + 1;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacing for the `progress` frames a long request writes (carrick#1914).
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The least time between two progress frames of one request.
|
|
6
|
+
*
|
|
7
|
+
* The scanner's deadline for a request is how long the sidecar may stay
|
|
8
|
+
* silent about it, so a frame has to say that a unit of work finished, and
|
|
9
|
+
* has to be far more frequent than that deadline. It does not have to follow
|
|
10
|
+
* every unit: a batch can finish thousands in a second.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PROGRESS_INTERVAL_MS = 1500;
|
|
13
|
+
/**
|
|
14
|
+
* `report`, held to one call per `intervalMs`: the first call goes through at
|
|
15
|
+
* once, and a call made sooner than `intervalMs` after the last one that went
|
|
16
|
+
* through is dropped.
|
|
17
|
+
*
|
|
18
|
+
* It is called by the work, between units, and never by a timer. A handler
|
|
19
|
+
* that stops finishing units stops reporting, which is what the reader on the
|
|
20
|
+
* other end is waiting to notice.
|
|
21
|
+
*/
|
|
22
|
+
export declare function atMostEvery<Args extends unknown[]>(intervalMs: number, report: (...args: Args) => void, now?: () => number): (...args: Args) => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacing for the `progress` frames a long request writes (carrick#1914).
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The least time between two progress frames of one request.
|
|
6
|
+
*
|
|
7
|
+
* The scanner's deadline for a request is how long the sidecar may stay
|
|
8
|
+
* silent about it, so a frame has to say that a unit of work finished, and
|
|
9
|
+
* has to be far more frequent than that deadline. It does not have to follow
|
|
10
|
+
* every unit: a batch can finish thousands in a second.
|
|
11
|
+
*/
|
|
12
|
+
export const PROGRESS_INTERVAL_MS = 1500;
|
|
13
|
+
/**
|
|
14
|
+
* `report`, held to one call per `intervalMs`: the first call goes through at
|
|
15
|
+
* once, and a call made sooner than `intervalMs` after the last one that went
|
|
16
|
+
* through is dropped.
|
|
17
|
+
*
|
|
18
|
+
* It is called by the work, between units, and never by a timer. A handler
|
|
19
|
+
* that stops finishing units stops reporting, which is what the reader on the
|
|
20
|
+
* other end is waiting to notice.
|
|
21
|
+
*/
|
|
22
|
+
export function atMostEvery(intervalMs, report, now = () => performance.now()) {
|
|
23
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
24
|
+
return (...args) => {
|
|
25
|
+
const at = now();
|
|
26
|
+
if (at - last < intervalMs)
|
|
27
|
+
return;
|
|
28
|
+
last = at;
|
|
29
|
+
report(...args);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { Node, SyntaxKind, ts } from 'ts-morph';
|
|
28
28
|
import { readsResponseStatus, statusesIn, SUCCEEDED, testsOnPath, } from './failure-path.js';
|
|
29
|
+
import { lineIndex } from './line-index.js';
|
|
29
30
|
import { fileDiagnostics } from './unwidened.js';
|
|
30
31
|
/** Names appended to a file that is not ours carry this prefix. */
|
|
31
32
|
const PREFIX = '__carrick_';
|
|
@@ -842,22 +843,3 @@ function mapBack(pos, edits) {
|
|
|
842
843
|
}
|
|
843
844
|
return { kind: 'original', pos: pos - shift };
|
|
844
845
|
}
|
|
845
|
-
/** 1-based line of an original-file position. */
|
|
846
|
-
function lineIndex(text) {
|
|
847
|
-
const starts = [0];
|
|
848
|
-
for (let i = 0; i < text.length; i++)
|
|
849
|
-
if (text[i] === '\n')
|
|
850
|
-
starts.push(i + 1);
|
|
851
|
-
return (pos) => {
|
|
852
|
-
let lo = 0;
|
|
853
|
-
let hi = starts.length - 1;
|
|
854
|
-
while (lo < hi) {
|
|
855
|
-
const mid = (lo + hi + 1) >> 1;
|
|
856
|
-
if (starts[mid] <= pos)
|
|
857
|
-
lo = mid;
|
|
858
|
-
else
|
|
859
|
-
hi = mid - 1;
|
|
860
|
-
}
|
|
861
|
-
return lo + 1;
|
|
862
|
-
};
|
|
863
|
-
}
|
|
@@ -63,6 +63,11 @@ export interface TypeInferrerOptions {
|
|
|
63
63
|
*/
|
|
64
64
|
unwidenedBudgetMs?: number;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Called each time an `infer` batch is done with one of its requests,
|
|
68
|
+
* whatever that request answered: skipped, refused, failed or inferred.
|
|
69
|
+
*/
|
|
70
|
+
export type InferRequestDone = () => void;
|
|
66
71
|
/**
|
|
67
72
|
* TypeInferrer - Extracts types from source code, both explicit and inferred
|
|
68
73
|
*
|
|
@@ -94,9 +99,10 @@ export declare class TypeInferrer {
|
|
|
94
99
|
*
|
|
95
100
|
* @param requests - Array of inference requests
|
|
96
101
|
* @param extractionConfig - Agent-generated extraction config for payload unwrapping
|
|
102
|
+
* @param onRequestDone - Called once per request, as the batch is done with it
|
|
97
103
|
* @returns InferResult with inferred types or errors
|
|
98
104
|
*/
|
|
99
|
-
infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig): InferResult;
|
|
105
|
+
infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig, onRequestDone?: InferRequestDone): InferResult;
|
|
100
106
|
/**
|
|
101
107
|
* carrick#1836: list on `result` the declarations behind the names its text
|
|
102
108
|
* prints that the request's file cannot resolve (`PrintedTypes.namesIn`).
|
|
@@ -350,12 +356,54 @@ export declare class TypeInferrer {
|
|
|
350
356
|
*/
|
|
351
357
|
private resultCarrierPayload;
|
|
352
358
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
|
|
359
|
+
* The shape test of `resultCarrierPayload`: the carrier `type` is, once a
|
|
360
|
+
* promise-like around it is peeled, and the type arguments a branch of it
|
|
361
|
+
* holds as a member. `undefined` when `type` is not a carrier.
|
|
362
|
+
*/
|
|
363
|
+
private resultCarrierArguments;
|
|
364
|
+
/**
|
|
365
|
+
* The decided abstain of a call whose result carries transport the
|
|
366
|
+
* service's wrapper rules verify and read no payload out of (carrick#1841,
|
|
367
|
+
* carrick#1843): `unknown` with `machinery_envelope` at the root and no
|
|
368
|
+
* anchor. The root reason is what keeps the capture's own locator from
|
|
369
|
+
* re-reading the raw call (`inference_decided_no_contract`,
|
|
370
|
+
* engine/type_compat_v2.rs).
|
|
371
|
+
*/
|
|
372
|
+
private transportAbstain;
|
|
373
|
+
/**
|
|
374
|
+
* `Future<T>` -> `T` for a thenable, read off the await protocol rather
|
|
375
|
+
* than a name: a `then` whose first parameter is a callback, whose own
|
|
376
|
+
* first parameter is the value awaiting it yields. `Promise` and
|
|
377
|
+
* `PromiseLike` are peeled by `unwrapPromiseType` before this.
|
|
378
|
+
*
|
|
379
|
+
* The callback is read through `null` and `undefined` (carrick#1877). A
|
|
380
|
+
* `then` of the source's own making declares `(value: T) => void`; the
|
|
381
|
+
* platform's declares `onfulfilled?: ((value: T) => ...) | null`, and that
|
|
382
|
+
* is the `then` a subclass of `Promise` inherits. Read as written, an
|
|
383
|
+
* optional, nullable callback has no call signature, and the subclass was
|
|
384
|
+
* not seen as a thenable at all.
|
|
385
|
+
*
|
|
386
|
+
* A `then` that cannot be called, or whose first parameter is no callback,
|
|
387
|
+
* is no protocol, and the type is returned as it is.
|
|
388
|
+
*
|
|
389
|
+
* The compiler's own awaited type arbitrates. This walk reads the first
|
|
390
|
+
* signature of `then`; the language reads all of them. Where awaiting the
|
|
391
|
+
* type and awaiting what this walk found are not the same thing to the
|
|
392
|
+
* compiler (an overloaded `then` whose first signature is not the one
|
|
393
|
+
* `await` takes), the walk did not read the protocol, and the type is
|
|
394
|
+
* returned as it is rather than published as a guess.
|
|
357
395
|
*/
|
|
358
396
|
private unwrapThenableType;
|
|
397
|
+
/** The value `then`'s first signature hands its callback, read until it stops changing. */
|
|
398
|
+
private firstThenValue;
|
|
399
|
+
/**
|
|
400
|
+
* What `await` yields for `type` where the names do not say: `type` is,
|
|
401
|
+
* once `Promise` and `PromiseLike` are peeled, a thenable by the protocol
|
|
402
|
+
* (a subclass of `Promise`, a class with a `then` of its own). `undefined`
|
|
403
|
+
* where the names say it all or `type` is no thenable, so a caller keeps
|
|
404
|
+
* the reading it had (carrick#1877).
|
|
405
|
+
*/
|
|
406
|
+
private awaitedBeyondPromise;
|
|
359
407
|
/**
|
|
360
408
|
* The platform's error shape, in full: `name` and `message` strings AND a
|
|
361
409
|
* `stack`, which is what the `Error` interface declares and every subclass
|
|
@@ -456,7 +504,17 @@ export declare class TypeInferrer {
|
|
|
456
504
|
*/
|
|
457
505
|
private castsOfUnreadParameter;
|
|
458
506
|
/**
|
|
459
|
-
* The zero-argument whole-body read
|
|
507
|
+
* The zero-argument whole-body read taken in place on the value `callExpr`
|
|
508
|
+
* yields, or `undefined` (carrick#1851): `(await fetch(url)).text()`. The
|
|
509
|
+
* receiver is the call itself, through the wrappers that leave a value as
|
|
510
|
+
* it is (parentheses, `await`, `!`), so it is the same read
|
|
511
|
+
* `bodyReadOnReceiver` finds on a binding of that value. A call that is not
|
|
512
|
+
* awaited first is read the same way: a request that is a promise and reads
|
|
513
|
+
* its own body (`send(url).json()`) yields the body from that read too.
|
|
514
|
+
*/
|
|
515
|
+
private bodyReadOnCallValue;
|
|
516
|
+
/**
|
|
517
|
+
* The zero-argument whole-body read that takes `receiver` as its receiver,
|
|
460
518
|
* `res.json()` or `res.text()`, or `undefined`. A text read is a body read
|
|
461
519
|
* like a json one (carrick#1842): without it, `return res.text()` left the
|
|
462
520
|
* walk on the response binding and published the transport object.
|
|
@@ -518,12 +576,27 @@ export declare class TypeInferrer {
|
|
|
518
576
|
* Try to unwrap a type using a single ExtractionRule.
|
|
519
577
|
*/
|
|
520
578
|
private tryUnwrapWithRule;
|
|
579
|
+
/**
|
|
580
|
+
* The names `type` goes by, own symbol first (carrick#1843).
|
|
581
|
+
*
|
|
582
|
+
* `type Task<A> = __Task<A>` has the class's symbol and arguments, and the
|
|
583
|
+
* alias's beside them. `type Reply<T> = { ... }` has the anonymous `__type`
|
|
584
|
+
* with no arguments of its own. `type Outcome<A, E> = Done<A, E> |
|
|
585
|
+
* Failed<A, E>` has no symbol of its own at all. In each, the alias and its
|
|
586
|
+
* arguments are what a rule naming `Task`, `Reply` or `Outcome` describes.
|
|
587
|
+
*/
|
|
588
|
+
private wrapperReadings;
|
|
521
589
|
/**
|
|
522
590
|
* Extract the payload type from a matched wrapper. Returns null when the
|
|
523
591
|
* rule matched the wrapper but no payload is recoverable from generics or
|
|
524
592
|
* property paths — the caller decides what a payload-less match means
|
|
525
593
|
* (verified machinery collapses to `unknown` after every rule has run;
|
|
526
594
|
* a name-only match leaves the type untouched).
|
|
595
|
+
*
|
|
596
|
+
* `payloadGenericIndex` counts the arguments of `reading`, the name the
|
|
597
|
+
* rule matched (carrick#1843): an alias is free to order its parameters
|
|
598
|
+
* differently from the type it stands for, so the same index into the
|
|
599
|
+
* other list is a different argument.
|
|
527
600
|
*/
|
|
528
601
|
private extractPayloadFromWrapper;
|
|
529
602
|
/**
|
|
@@ -920,11 +993,19 @@ export declare class TypeInferrer {
|
|
|
920
993
|
/**
|
|
921
994
|
* Declaration file (absolute path) of the anchor symbol
|
|
922
995
|
* `primaryTypeSymbol` reports for this type, or `undefined` when the type
|
|
923
|
-
* has no user-facing anchor or no source declaration.
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
*
|
|
996
|
+
* has no user-facing anchor or no source declaration. Every path that
|
|
997
|
+
* reports the symbol reports this beside it (carrick#1819): it is where a
|
|
998
|
+
* reader finds the type an anchor names. The scanner's pub/sub two-anchor
|
|
999
|
+
* arbitration (carrick#413) also uses it to re-aim a demoted explicit
|
|
1000
|
+
* bundle request: the bundler resolves a `SymbolRequest` only against
|
|
1001
|
+
* declarations IN its `source_file`, so the request must point at the file
|
|
1002
|
+
* that actually declares the tsc-witnessed payload type.
|
|
1003
|
+
*
|
|
1004
|
+
* The declarations read are those of the type's own symbol, so a name
|
|
1005
|
+
* imported through a barrel reports the file that declares it, and a name
|
|
1006
|
+
* two files declare reports the one this type resolves to. A declaration in
|
|
1007
|
+
* an installed package or a TypeScript lib is reported as it is found, as
|
|
1008
|
+
* an absolute path; what a reader is shown for it is the scanner's call.
|
|
928
1009
|
*
|
|
929
1010
|
* Only declaration kinds the bundler's `validateSymbols` can resolve
|
|
930
1011
|
* (interface, type alias, class, enum, function, variable) count. A
|