@cplieger/deadset-ts 3.13.0 → 4.0.0
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 +3 -3
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +119 -23
- package/dist/src/calls.js +23 -22
- package/dist/src/class-members.js +10 -11
- package/dist/src/configured-declarations.js +22 -9
- package/dist/src/dependencies.js +34 -17
- package/dist/src/deprecation.js +35 -24
- package/dist/src/destructuring.js +37 -29
- package/dist/src/discover.js +105 -28
- package/dist/src/entry-points.js +7 -0
- package/dist/src/enum-group.js +46 -40
- package/dist/src/exempt.js +42 -1
- package/dist/src/findings/emitters.js +15 -1
- package/dist/src/findings/interfaces.js +7 -1
- package/dist/src/framework-lifecycle.js +8 -6
- package/dist/src/implementations.js +45 -23
- package/dist/src/interface-satisfaction.js +30 -27
- package/dist/src/intra-function-parts.js +37 -20
- package/dist/src/inventory.js +85 -4
- package/dist/src/json-chunks.js +76 -0
- package/dist/src/partial.js +23 -0
- package/dist/src/query.js +207 -0
- package/dist/src/references.js +92 -20
- package/dist/src/reflective-lookup.js +18 -9
- package/dist/src/report.js +3 -1
- package/dist/src/reporters/annotations.js +9 -5
- package/dist/src/reporters/json.js +3 -2
- package/dist/src/reporters/sarif.js +2 -1
- package/dist/src/reporters/template.js +48 -7
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/serialization-contract.js +12 -10
- package/dist/src/session.js +79 -39
- package/dist/src/suppress-file.js +2 -1
- package/dist/src/sweep.js +4 -1
- package/dist/src/value-reach.js +14 -19
- package/dist/src/verbs/analyze.js +20 -3
- package/dist/src/verbs/explain.js +22 -9
- package/dist/src/verbs/print-projects.js +40 -7
- package/dist/src/verbs/print-retained.js +6 -2
- package/dist/src/verbs/print-roots.js +5 -1
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +75 -11
- package/src/analysis.ts +223 -61
- package/src/calls.ts +10 -9
- package/src/class-members.ts +10 -11
- package/src/configured-declarations.ts +29 -13
- package/src/dependencies.ts +42 -20
- package/src/deprecation.ts +53 -27
- package/src/destructuring.ts +50 -39
- package/src/discover.ts +177 -33
- package/src/entry-points.ts +7 -0
- package/src/enum-group.ts +56 -42
- package/src/exempt.ts +50 -2
- package/src/findings/emitters.ts +18 -1
- package/src/findings/interfaces.ts +7 -1
- package/src/framework-lifecycle.ts +10 -6
- package/src/host.ts +5 -3
- package/src/implementations.ts +59 -23
- package/src/interface-satisfaction.ts +30 -27
- package/src/intra-function-parts.ts +43 -25
- package/src/inventory.ts +97 -4
- package/src/json-chunks.ts +87 -0
- package/src/partial.ts +40 -0
- package/src/query.ts +522 -0
- package/src/references.ts +119 -38
- package/src/reflective-lookup.ts +21 -11
- package/src/report.ts +14 -2
- package/src/reporters/annotations.ts +23 -21
- package/src/reporters/json.ts +3 -2
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/sarif.ts +3 -2
- package/src/reporters/template.ts +50 -8
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/serialization-contract.ts +12 -10
- package/src/session.ts +146 -58
- package/src/suppress-file.ts +6 -2
- package/src/sweep.ts +9 -4
- package/src/value-reach.ts +20 -23
- package/src/verbs/analyze.ts +25 -3
- package/src/verbs/explain.ts +20 -9
- package/src/verbs/print-projects.ts +53 -7
- package/src/verbs/print-retained.ts +6 -2
- package/src/verbs/print-roots.ts +5 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A document written in pieces, so no stage of writing it holds the whole document as
|
|
3
|
+
* one string: a string has a length limit far below what a large report reaches.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The length a piece grows to before it is handed on. */
|
|
7
|
+
export const CHUNK_LENGTH = 1 << 16;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The pieces of `pieces` joined into runs of about {@link CHUNK_LENGTH}, the last one
|
|
11
|
+
* shorter. A piece longer than that is handed on whole.
|
|
12
|
+
*/
|
|
13
|
+
export function* coalesced(pieces: Iterable<string>): Generator<string, void, undefined> {
|
|
14
|
+
let held: string[] = [];
|
|
15
|
+
let length = 0;
|
|
16
|
+
for (const piece of pieces) {
|
|
17
|
+
held.push(piece);
|
|
18
|
+
length += piece.length;
|
|
19
|
+
if (length >= CHUNK_LENGTH) {
|
|
20
|
+
yield held.join("");
|
|
21
|
+
held = [];
|
|
22
|
+
length = 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (length > 0) {
|
|
26
|
+
yield held.join("");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Whether `JSON.stringify` writes nothing for a value where an object member holds it. */
|
|
31
|
+
function omitted(value: unknown): boolean {
|
|
32
|
+
return value === undefined || typeof value === "function" || typeof value === "symbol";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function* valueOf(
|
|
36
|
+
value: unknown,
|
|
37
|
+
prefix: string,
|
|
38
|
+
step: string,
|
|
39
|
+
): Generator<string, void, undefined> {
|
|
40
|
+
// With no indentation the document is compact: no line breaks, and no space after a key.
|
|
41
|
+
const nl = step === "" ? "" : "\n";
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
if (value.length === 0) {
|
|
44
|
+
yield "[]";
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const inner = prefix + step;
|
|
48
|
+
yield "[";
|
|
49
|
+
for (let at = 0; at < value.length; at += 1) {
|
|
50
|
+
yield at === 0 ? `${nl}${inner}` : `,${nl}${inner}`;
|
|
51
|
+
const element: unknown = value[at];
|
|
52
|
+
yield* omitted(element) ? ["null"] : valueOf(element, inner, step);
|
|
53
|
+
}
|
|
54
|
+
yield `${nl}${prefix}]`;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === "object" && value !== null) {
|
|
58
|
+
const inner = prefix + step;
|
|
59
|
+
let first = true;
|
|
60
|
+
for (const [key, member] of Object.entries(value)) {
|
|
61
|
+
if (omitted(member)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
yield `${first ? "{" : ","}${nl}${inner}${JSON.stringify(key)}:${step === "" ? "" : " "}`;
|
|
65
|
+
first = false;
|
|
66
|
+
yield* valueOf(member, inner, step);
|
|
67
|
+
}
|
|
68
|
+
yield first ? "{}" : `${nl}${prefix}}`;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
yield JSON.stringify(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* One JSON document in pieces whose concatenation is the value as `JSON.stringify`
|
|
76
|
+
* writes it at `indent` spaces, followed by a newline. The value is plain data: objects,
|
|
77
|
+
* arrays, strings, finite numbers, booleans and null, an object member holding nothing
|
|
78
|
+
* being left out as `JSON.stringify` leaves it out.
|
|
79
|
+
*/
|
|
80
|
+
export function jsonChunks(value: unknown, indent: number): Generator<string, void, undefined> {
|
|
81
|
+
return coalesced(
|
|
82
|
+
(function* document(): Generator<string, void, undefined> {
|
|
83
|
+
yield* valueOf(value, "", " ".repeat(indent));
|
|
84
|
+
yield "\n";
|
|
85
|
+
})(),
|
|
86
|
+
);
|
|
87
|
+
}
|
package/src/partial.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { NotBuilt } from "./discover.ts";
|
|
2
|
+
import type { UnansweredQuestion } from "./query.ts";
|
|
3
|
+
|
|
4
|
+
/** What a run read only in part. */
|
|
5
|
+
interface Partial {
|
|
6
|
+
/** The derived configurations the run dropped. */
|
|
7
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
8
|
+
/** Every question the checker could not answer, each once. */
|
|
9
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A count with its noun, so a line reads for one as well as for several. */
|
|
13
|
+
function counted(n: number, one: string, many: string): string {
|
|
14
|
+
return `${String(n)} ${n === 1 ? one : many}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* One line per configuration the run dropped, and one counting the questions the checker
|
|
19
|
+
* left unanswered by project, so a reader of the error stream sees where the analysis was
|
|
20
|
+
* partial. The lines carry no prefix; the caller writes them as its own diagnostics.
|
|
21
|
+
*/
|
|
22
|
+
export function partialNotes(partial: Partial): readonly string[] {
|
|
23
|
+
const notes = [...partial.notBuilt]
|
|
24
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
|
25
|
+
.map(
|
|
26
|
+
(one) =>
|
|
27
|
+
`the derived configuration ${one.id} was not built and is not analyzed: ${one.error}`,
|
|
28
|
+
);
|
|
29
|
+
if (partial.unanswered.length > 0) {
|
|
30
|
+
const byProject = new Map<string, number>();
|
|
31
|
+
for (const question of partial.unanswered) {
|
|
32
|
+
byProject.set(question.configFile, (byProject.get(question.configFile) ?? 0) + 1);
|
|
33
|
+
}
|
|
34
|
+
const where = [...byProject].map(([configFile, n]) => `${configFile}: ${String(n)}`).join(", ");
|
|
35
|
+
notes.push(
|
|
36
|
+
`the checker answered ${counted(partial.unanswered.length, "question", "questions")} with a failure (${where}); every declaration an answer could have kept live is kept live and reported by nothing`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return notes;
|
|
40
|
+
}
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way the analysis asks a project's checker anything. A question the compiler
|
|
3
|
+
* server panics on answers {@link UNANSWERED}, and a batch it panics on whole is asked
|
|
4
|
+
* again one question at a time; every caller reads an unanswered question as keeping a
|
|
5
|
+
* declaration live, and each is recorded once so a run can say where it was partial.
|
|
6
|
+
* Questions of one accessor about many locations go out together, through the array
|
|
7
|
+
* form where it has one and otherwise as one batch, one round trip per capped run.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isIdentifier, type Expression, type Node } from "@typescript/native/unstable/ast";
|
|
11
|
+
import type {
|
|
12
|
+
APIRequestGenerator,
|
|
13
|
+
Checker,
|
|
14
|
+
IndexInfo,
|
|
15
|
+
JSDocTagInfo,
|
|
16
|
+
Signature,
|
|
17
|
+
SignatureKind,
|
|
18
|
+
SymbolFlags,
|
|
19
|
+
Symbol as TSSymbol,
|
|
20
|
+
Type,
|
|
21
|
+
} from "@typescript/native/unstable/sync";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The greatest number of locations one batched request asks about. A batch is
|
|
25
|
+
* answered by an array of its own length, so an uncapped batch over a large file
|
|
26
|
+
* makes one answer proportional to that file. The figure bounds the size of one
|
|
27
|
+
* answer rather than the work: the median file is one batch at this cap and the
|
|
28
|
+
* largest production file a handful.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_BATCH_CAP = 4096;
|
|
31
|
+
|
|
32
|
+
/** The answer to a question the checker could not answer. */
|
|
33
|
+
export const UNANSWERED: unique symbol = Symbol("unanswered");
|
|
34
|
+
|
|
35
|
+
/** A checker's answer, or {@link UNANSWERED} where the server panicked on the question. */
|
|
36
|
+
export type Answer<T> = T | typeof UNANSWERED;
|
|
37
|
+
|
|
38
|
+
/** Whether one answer is an answer. */
|
|
39
|
+
export function isAnswered<T>(answer: Answer<T>): answer is T {
|
|
40
|
+
return answer !== UNANSWERED;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The stop a pass raises where it cannot go on without an answer the checker did not
|
|
45
|
+
* give. Whoever runs the pass catches it and keeps live everything the pass could
|
|
46
|
+
* have kept live, so a pass that throws it never computes a finding from the gap.
|
|
47
|
+
*/
|
|
48
|
+
export class Unanswerable extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super("the checker did not answer a question this pass needs");
|
|
51
|
+
this.name = "Unanswerable";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The answer itself, or the stop of a pass that cannot go on without it. */
|
|
56
|
+
export function must<T>(answer: Answer<T>): T {
|
|
57
|
+
if (answer === UNANSWERED) {
|
|
58
|
+
throw new Unanswerable();
|
|
59
|
+
}
|
|
60
|
+
return answer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether one failure is the server's report that a question panicked. The server
|
|
65
|
+
* recovers the panic and answers the question with its text, so the session
|
|
66
|
+
* survives it; any other failure, a closed channel among them, is not one this layer
|
|
67
|
+
* contains.
|
|
68
|
+
*/
|
|
69
|
+
function panicked(error: unknown): boolean {
|
|
70
|
+
return error instanceof Error && error.message.startsWith("panic:");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Runs many questions in one round trip, answering each in order. */
|
|
74
|
+
export type Batch = <T>(questions: readonly APIRequestGenerator<T>[]) => T[];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Puts one question to the checker, by its accessor's name and the locations it asks
|
|
78
|
+
* about, and answers what it answers.
|
|
79
|
+
*/
|
|
80
|
+
type Ask = <T>(accessor: string, locations: () => readonly string[], question: () => T) => T;
|
|
81
|
+
|
|
82
|
+
const ASK_DIRECTLY: Ask = (_accessor, _locations, question) => question();
|
|
83
|
+
|
|
84
|
+
/** One question the checker left unanswered, by the project it was asked in. */
|
|
85
|
+
export interface UnansweredQuestion {
|
|
86
|
+
/** The compiler configuration file of the project the question was asked in. */
|
|
87
|
+
readonly configFile: string;
|
|
88
|
+
/** The accessor that panicked. */
|
|
89
|
+
readonly accessor: string;
|
|
90
|
+
/** What was asked about: a file and offset, or a symbol's or a type's identity. */
|
|
91
|
+
readonly location: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** What one run could not ask, each question once. */
|
|
95
|
+
export class UnansweredLog {
|
|
96
|
+
readonly #seen = new Map<string, UnansweredQuestion>();
|
|
97
|
+
|
|
98
|
+
/** Records one question, once however many times it is asked. */
|
|
99
|
+
note(question: UnansweredQuestion): void {
|
|
100
|
+
const key = [question.configFile, question.accessor, question.location].join("\u0000");
|
|
101
|
+
if (!this.#seen.has(key)) {
|
|
102
|
+
this.#seen.set(key, question);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Every question recorded, in the order first asked. */
|
|
107
|
+
get questions(): readonly UnansweredQuestion[] {
|
|
108
|
+
return [...this.#seen.values()];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function nodeLocation(node: Node): string {
|
|
113
|
+
return `${node.getSourceFile().fileName}:${String(node.pos)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function symbolLocation(symbol: TSSymbol): string {
|
|
117
|
+
return `symbol ${String(symbol.id)} ${symbol.name}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function typeLocation(type: Type): string {
|
|
121
|
+
return `type ${String(type.id)}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** One assignability question. */
|
|
125
|
+
interface Assignability {
|
|
126
|
+
readonly source: Type;
|
|
127
|
+
readonly target: Type;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The questions one project's analysis asks, each guarded. */
|
|
131
|
+
export interface Queries {
|
|
132
|
+
/** The symbol each node resolves to. */
|
|
133
|
+
symbolsAt(nodes: readonly Node[], cap?: number): Answer<TSSymbol | undefined>[];
|
|
134
|
+
/** The symbol one identifier names by name lookup at its position; nothing for another node. */
|
|
135
|
+
resolvedSymbol(node: Node): Answer<TSSymbol | undefined>;
|
|
136
|
+
/** The symbol the value of one shorthand property assignment names. */
|
|
137
|
+
shorthandValue(node: Node): Answer<TSSymbol | undefined>;
|
|
138
|
+
/** The symbol one alias names, one link along. */
|
|
139
|
+
immediateAliased(symbol: TSSymbol): Answer<TSSymbol | undefined>;
|
|
140
|
+
/** The symbol at the end of one alias's chain. */
|
|
141
|
+
aliased(symbol: TSSymbol): Answer<TSSymbol>;
|
|
142
|
+
resolveName(
|
|
143
|
+
name: string,
|
|
144
|
+
meaning: SymbolFlags,
|
|
145
|
+
location: Node | undefined,
|
|
146
|
+
excludeGlobals: boolean,
|
|
147
|
+
): Answer<TSSymbol | undefined>;
|
|
148
|
+
exportsOfModule(module: TSSymbol): Answer<readonly TSSymbol[]>;
|
|
149
|
+
/** One symbol's member table. */
|
|
150
|
+
membersOf(symbol: TSSymbol): Answer<ReadonlyMap<string, TSSymbol>>;
|
|
151
|
+
/** One symbol's export table. */
|
|
152
|
+
exportsOf(symbol: TSSymbol): Answer<ReadonlyMap<string, TSSymbol>>;
|
|
153
|
+
/** The symbol that declares one symbol as a member. */
|
|
154
|
+
parentOf(symbol: TSSymbol): Answer<TSSymbol | undefined>;
|
|
155
|
+
/** The documentation tags of each symbol. */
|
|
156
|
+
jsDocTags(symbols: readonly TSSymbol[], cap?: number): Answer<readonly JSDocTagInfo[]>[];
|
|
157
|
+
/** The type at each node. */
|
|
158
|
+
typesAt(nodes: readonly Node[], cap?: number): Answer<Type | undefined>[];
|
|
159
|
+
typeAt(node: Node): Answer<Type | undefined>;
|
|
160
|
+
/** The type of each symbol. */
|
|
161
|
+
typesOfSymbols(symbols: readonly TSSymbol[], cap?: number): Answer<Type | undefined>[];
|
|
162
|
+
typeOfSymbol(symbol: TSSymbol): Answer<Type | undefined>;
|
|
163
|
+
declaredTypeOf(symbol: TSSymbol): Answer<Type>;
|
|
164
|
+
/** The type each value position expects. */
|
|
165
|
+
contextualTypes(nodes: readonly Expression[], cap?: number): Answer<Type | undefined>[];
|
|
166
|
+
propertiesOf(type: Type): Answer<readonly TSSymbol[]>;
|
|
167
|
+
indexInfosOf(type: Type): Answer<readonly IndexInfo[]>;
|
|
168
|
+
/** The type arguments of a type reference; none for another type. */
|
|
169
|
+
typeArguments(type: Type): Answer<readonly Type[]>;
|
|
170
|
+
nonNullable(type: Type): Answer<Type>;
|
|
171
|
+
isArray(type: Type): Answer<boolean>;
|
|
172
|
+
/** Whether each source is assignable to its target. */
|
|
173
|
+
assignableEach(pairs: readonly Assignability[], cap?: number): Answer<boolean>[];
|
|
174
|
+
/** The base types of a class or an interface; none for another type. */
|
|
175
|
+
baseTypes(type: Type): Answer<readonly Type[]>;
|
|
176
|
+
/** The constituents of a union or an intersection; the type itself for another type. */
|
|
177
|
+
constituents(type: Type): Answer<readonly Type[]>;
|
|
178
|
+
/** The generic type a type reference instantiates; the type itself for another type. */
|
|
179
|
+
targetOf(type: Type): Answer<Type>;
|
|
180
|
+
/** The symbol a type is declared by. */
|
|
181
|
+
symbolOfType(type: Type): Answer<TSSymbol | undefined>;
|
|
182
|
+
signaturesOf(type: Type, kind: SignatureKind): Answer<readonly Signature[]>;
|
|
183
|
+
resolvedSignature(node: Node): Answer<Signature | undefined>;
|
|
184
|
+
parameterType(signature: Signature, index: number): Answer<Type | undefined>;
|
|
185
|
+
signatureOf(declaration: Node): Answer<Signature | undefined>;
|
|
186
|
+
returnTypeOf(signature: Signature): Answer<Type | undefined>;
|
|
187
|
+
typeToString(type: Type): Answer<string>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The guarded questions over one project's checker. `configFile` names the project
|
|
192
|
+
* in the log, `batch` sends many questions in one round trip, `log` is the run's,
|
|
193
|
+
* shared by every project, and every question is put through `ask`.
|
|
194
|
+
*/
|
|
195
|
+
export function queriesOf(
|
|
196
|
+
checker: Checker,
|
|
197
|
+
configFile: string,
|
|
198
|
+
batch: Batch,
|
|
199
|
+
log: UnansweredLog,
|
|
200
|
+
ask: Ask = ASK_DIRECTLY,
|
|
201
|
+
): Queries {
|
|
202
|
+
const note = (accessor: string, location: string): void => {
|
|
203
|
+
log.note({ configFile, accessor, location });
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const one = <T>(accessor: string, location: () => string, question: () => T): Answer<T> => {
|
|
207
|
+
try {
|
|
208
|
+
return ask(accessor, () => [location()], question);
|
|
209
|
+
} catch (error: unknown) {
|
|
210
|
+
if (!panicked(error)) {
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
note(accessor, location());
|
|
214
|
+
return UNANSWERED;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* One question per item of `items`, in runs of at most `cap`, each run in one
|
|
220
|
+
* round trip. A run fails as a whole only where the server panics outside any one
|
|
221
|
+
* question, and is then asked one question at a time.
|
|
222
|
+
*/
|
|
223
|
+
const each = <In, Out>(
|
|
224
|
+
accessor: string,
|
|
225
|
+
items: readonly In[],
|
|
226
|
+
cap: number,
|
|
227
|
+
locate: (item: In) => string,
|
|
228
|
+
question: (item: In) => APIRequestGenerator<Out>,
|
|
229
|
+
alone: (item: In) => Out,
|
|
230
|
+
): Answer<Out>[] => {
|
|
231
|
+
function* guarded(item: In): APIRequestGenerator<Answer<Out>> {
|
|
232
|
+
try {
|
|
233
|
+
return yield* ask(
|
|
234
|
+
accessor,
|
|
235
|
+
() => [locate(item)],
|
|
236
|
+
() => question(item),
|
|
237
|
+
);
|
|
238
|
+
} catch (error: unknown) {
|
|
239
|
+
if (!panicked(error)) {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
note(accessor, locate(item));
|
|
243
|
+
return UNANSWERED;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const answers: Answer<Out>[] = [];
|
|
247
|
+
for (let from = 0; from < items.length; from += cap) {
|
|
248
|
+
const run = items.slice(from, from + cap);
|
|
249
|
+
try {
|
|
250
|
+
answers.push(...batch(run.map(guarded)));
|
|
251
|
+
} catch (error: unknown) {
|
|
252
|
+
if (!panicked(error)) {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
answers.push(
|
|
256
|
+
...run.map((item) =>
|
|
257
|
+
one(
|
|
258
|
+
accessor,
|
|
259
|
+
() => locate(item),
|
|
260
|
+
() => alone(item),
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return answers;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* One accessor's array form over `items`, in runs of at most `cap`. A run the server
|
|
271
|
+
* panics on is asked again as two halves, down to one item, so the items it can
|
|
272
|
+
* answer are answered and only an item that panics alone is unanswered.
|
|
273
|
+
*/
|
|
274
|
+
const many = <In, Out>(
|
|
275
|
+
accessor: string,
|
|
276
|
+
items: readonly In[],
|
|
277
|
+
cap: number,
|
|
278
|
+
locate: (item: In) => string,
|
|
279
|
+
question: (run: readonly In[]) => readonly Out[],
|
|
280
|
+
): Answer<Out>[] => {
|
|
281
|
+
const answers: Answer<Out>[] = [];
|
|
282
|
+
const settle = (run: readonly In[]): void => {
|
|
283
|
+
try {
|
|
284
|
+
answers.push(
|
|
285
|
+
...ask(
|
|
286
|
+
accessor,
|
|
287
|
+
() => run.map(locate),
|
|
288
|
+
() => question(run),
|
|
289
|
+
),
|
|
290
|
+
);
|
|
291
|
+
return;
|
|
292
|
+
} catch (error: unknown) {
|
|
293
|
+
if (!panicked(error)) {
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const [only] = run;
|
|
298
|
+
if (run.length === 1 && only !== undefined) {
|
|
299
|
+
note(accessor, locate(only));
|
|
300
|
+
answers.push(UNANSWERED);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const half = Math.ceil(run.length / 2);
|
|
304
|
+
settle(run.slice(0, half));
|
|
305
|
+
settle(run.slice(half));
|
|
306
|
+
};
|
|
307
|
+
for (let from = 0; from < items.length; from += cap) {
|
|
308
|
+
settle(items.slice(from, from + cap));
|
|
309
|
+
}
|
|
310
|
+
return answers;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
symbolsAt: (nodes, cap = DEFAULT_BATCH_CAP) =>
|
|
315
|
+
many("getSymbolAtLocation", nodes, cap, nodeLocation, (run) =>
|
|
316
|
+
checker.getSymbolAtLocation(run),
|
|
317
|
+
),
|
|
318
|
+
resolvedSymbol: (node) =>
|
|
319
|
+
isIdentifier(node)
|
|
320
|
+
? one(
|
|
321
|
+
"getResolvedSymbol",
|
|
322
|
+
() => nodeLocation(node),
|
|
323
|
+
() => checker.getResolvedSymbol(node),
|
|
324
|
+
)
|
|
325
|
+
: undefined,
|
|
326
|
+
shorthandValue: (node) =>
|
|
327
|
+
one(
|
|
328
|
+
"getShorthandAssignmentValueSymbol",
|
|
329
|
+
() => nodeLocation(node),
|
|
330
|
+
() => checker.getShorthandAssignmentValueSymbol(node),
|
|
331
|
+
),
|
|
332
|
+
immediateAliased: (symbol) =>
|
|
333
|
+
one(
|
|
334
|
+
"getImmediateAliasedSymbol",
|
|
335
|
+
() => symbolLocation(symbol),
|
|
336
|
+
() => checker.getImmediateAliasedSymbol(symbol),
|
|
337
|
+
),
|
|
338
|
+
aliased: (symbol) =>
|
|
339
|
+
one(
|
|
340
|
+
"getAliasedSymbol",
|
|
341
|
+
() => symbolLocation(symbol),
|
|
342
|
+
() => checker.getAliasedSymbol(symbol),
|
|
343
|
+
),
|
|
344
|
+
resolveName: (name, meaning, location, excludeGlobals) =>
|
|
345
|
+
one(
|
|
346
|
+
"resolveName",
|
|
347
|
+
() => (location === undefined ? `name ${name}` : nodeLocation(location)),
|
|
348
|
+
() => checker.resolveName(name, meaning, location, excludeGlobals),
|
|
349
|
+
),
|
|
350
|
+
exportsOfModule: (module) =>
|
|
351
|
+
one(
|
|
352
|
+
"getExportsOfModule",
|
|
353
|
+
() => symbolLocation(module),
|
|
354
|
+
() => checker.getExportsOfModule(module),
|
|
355
|
+
),
|
|
356
|
+
membersOf: (symbol) =>
|
|
357
|
+
one(
|
|
358
|
+
"getMembers",
|
|
359
|
+
() => symbolLocation(symbol),
|
|
360
|
+
() => symbol.getMembers(),
|
|
361
|
+
),
|
|
362
|
+
exportsOf: (symbol) =>
|
|
363
|
+
one(
|
|
364
|
+
"getExports",
|
|
365
|
+
() => symbolLocation(symbol),
|
|
366
|
+
() => symbol.getExports(),
|
|
367
|
+
),
|
|
368
|
+
parentOf: (symbol) =>
|
|
369
|
+
one(
|
|
370
|
+
"getParent",
|
|
371
|
+
() => symbolLocation(symbol),
|
|
372
|
+
() => symbol.getParent(),
|
|
373
|
+
),
|
|
374
|
+
jsDocTags: (symbols, cap = DEFAULT_BATCH_CAP) =>
|
|
375
|
+
each(
|
|
376
|
+
"getJsDocTags",
|
|
377
|
+
symbols,
|
|
378
|
+
cap,
|
|
379
|
+
symbolLocation,
|
|
380
|
+
(symbol) => symbol.getJsDocTags.gen(checker),
|
|
381
|
+
(symbol) => symbol.getJsDocTags(checker),
|
|
382
|
+
),
|
|
383
|
+
typesAt: (nodes, cap = DEFAULT_BATCH_CAP) =>
|
|
384
|
+
many("getTypeAtLocation", nodes, cap, nodeLocation, (run) => checker.getTypeAtLocation(run)),
|
|
385
|
+
typeAt: (node) =>
|
|
386
|
+
one(
|
|
387
|
+
"getTypeAtLocation",
|
|
388
|
+
() => nodeLocation(node),
|
|
389
|
+
() => checker.getTypeAtLocation(node),
|
|
390
|
+
),
|
|
391
|
+
typesOfSymbols: (symbols, cap = DEFAULT_BATCH_CAP) =>
|
|
392
|
+
many("getTypeOfSymbol", symbols, cap, symbolLocation, (run) => checker.getTypeOfSymbol(run)),
|
|
393
|
+
typeOfSymbol: (symbol) =>
|
|
394
|
+
one(
|
|
395
|
+
"getTypeOfSymbol",
|
|
396
|
+
() => symbolLocation(symbol),
|
|
397
|
+
() => checker.getTypeOfSymbol(symbol),
|
|
398
|
+
),
|
|
399
|
+
declaredTypeOf: (symbol) =>
|
|
400
|
+
one(
|
|
401
|
+
"getDeclaredTypeOfSymbol",
|
|
402
|
+
() => symbolLocation(symbol),
|
|
403
|
+
() => checker.getDeclaredTypeOfSymbol(symbol),
|
|
404
|
+
),
|
|
405
|
+
contextualTypes: (nodes, cap = DEFAULT_BATCH_CAP) =>
|
|
406
|
+
each(
|
|
407
|
+
"getContextualType",
|
|
408
|
+
nodes,
|
|
409
|
+
cap,
|
|
410
|
+
nodeLocation,
|
|
411
|
+
(node) => checker.getContextualType.gen(node),
|
|
412
|
+
(node) => checker.getContextualType(node),
|
|
413
|
+
),
|
|
414
|
+
propertiesOf: (type) =>
|
|
415
|
+
one(
|
|
416
|
+
"getPropertiesOfType",
|
|
417
|
+
() => typeLocation(type),
|
|
418
|
+
() => checker.getPropertiesOfType(type),
|
|
419
|
+
),
|
|
420
|
+
indexInfosOf: (type) =>
|
|
421
|
+
one(
|
|
422
|
+
"getIndexInfosOfType",
|
|
423
|
+
() => typeLocation(type),
|
|
424
|
+
() => checker.getIndexInfosOfType(type),
|
|
425
|
+
),
|
|
426
|
+
typeArguments: (type) =>
|
|
427
|
+
type.isTypeReference()
|
|
428
|
+
? one(
|
|
429
|
+
"getTypeArguments",
|
|
430
|
+
() => typeLocation(type),
|
|
431
|
+
() => checker.getTypeArguments(type),
|
|
432
|
+
)
|
|
433
|
+
: [],
|
|
434
|
+
nonNullable: (type) =>
|
|
435
|
+
one(
|
|
436
|
+
"getNonNullableType",
|
|
437
|
+
() => typeLocation(type),
|
|
438
|
+
() => checker.getNonNullableType(type),
|
|
439
|
+
),
|
|
440
|
+
isArray: (type) =>
|
|
441
|
+
one(
|
|
442
|
+
"isArrayType",
|
|
443
|
+
() => typeLocation(type),
|
|
444
|
+
() => checker.isArrayType(type),
|
|
445
|
+
),
|
|
446
|
+
assignableEach: (pairs, cap = DEFAULT_BATCH_CAP) =>
|
|
447
|
+
each(
|
|
448
|
+
"isTypeAssignableTo",
|
|
449
|
+
pairs,
|
|
450
|
+
cap,
|
|
451
|
+
(pair) => `${typeLocation(pair.source)} to ${typeLocation(pair.target)}`,
|
|
452
|
+
(pair) => checker.isTypeAssignableTo.gen(pair.source, pair.target),
|
|
453
|
+
(pair) => checker.isTypeAssignableTo(pair.source, pair.target),
|
|
454
|
+
),
|
|
455
|
+
baseTypes: (type) =>
|
|
456
|
+
type.isClassOrInterface()
|
|
457
|
+
? one(
|
|
458
|
+
"getBaseTypes",
|
|
459
|
+
() => typeLocation(type),
|
|
460
|
+
() => checker.getBaseTypes(type),
|
|
461
|
+
)
|
|
462
|
+
: [],
|
|
463
|
+
constituents: (type) =>
|
|
464
|
+
type.isUnionType() || type.isIntersectionType()
|
|
465
|
+
? one(
|
|
466
|
+
"getTypes",
|
|
467
|
+
() => typeLocation(type),
|
|
468
|
+
() => type.getTypes(),
|
|
469
|
+
)
|
|
470
|
+
: [type],
|
|
471
|
+
targetOf: (type) =>
|
|
472
|
+
type.isTypeReference()
|
|
473
|
+
? one(
|
|
474
|
+
"getTarget",
|
|
475
|
+
() => typeLocation(type),
|
|
476
|
+
() => type.getTarget(),
|
|
477
|
+
)
|
|
478
|
+
: type,
|
|
479
|
+
symbolOfType: (type) =>
|
|
480
|
+
one(
|
|
481
|
+
"getSymbol",
|
|
482
|
+
() => typeLocation(type),
|
|
483
|
+
() => type.getSymbol(),
|
|
484
|
+
),
|
|
485
|
+
signaturesOf: (type, kind) =>
|
|
486
|
+
one(
|
|
487
|
+
"getSignaturesOfType",
|
|
488
|
+
() => typeLocation(type),
|
|
489
|
+
() => checker.getSignaturesOfType(type, kind),
|
|
490
|
+
),
|
|
491
|
+
resolvedSignature: (node) =>
|
|
492
|
+
one(
|
|
493
|
+
"getResolvedSignature",
|
|
494
|
+
() => nodeLocation(node),
|
|
495
|
+
() => checker.getResolvedSignature(node),
|
|
496
|
+
),
|
|
497
|
+
parameterType: (signature, index) =>
|
|
498
|
+
one(
|
|
499
|
+
"getParameterType",
|
|
500
|
+
() => `signature ${String(signature.id)} parameter ${String(index)}`,
|
|
501
|
+
() => checker.getParameterType(signature, index),
|
|
502
|
+
),
|
|
503
|
+
signatureOf: (declaration) =>
|
|
504
|
+
one(
|
|
505
|
+
"getSignatureFromDeclaration",
|
|
506
|
+
() => nodeLocation(declaration),
|
|
507
|
+
() => checker.getSignatureFromDeclaration(declaration),
|
|
508
|
+
),
|
|
509
|
+
returnTypeOf: (signature) =>
|
|
510
|
+
one(
|
|
511
|
+
"getReturnTypeOfSignature",
|
|
512
|
+
() => `signature ${String(signature.id)}`,
|
|
513
|
+
() => checker.getReturnTypeOfSignature(signature),
|
|
514
|
+
),
|
|
515
|
+
typeToString: (type) =>
|
|
516
|
+
one(
|
|
517
|
+
"typeToString",
|
|
518
|
+
() => typeLocation(type),
|
|
519
|
+
() => checker.typeToString(type),
|
|
520
|
+
),
|
|
521
|
+
};
|
|
522
|
+
}
|