@cplieger/deadset-ts 3.12.1 → 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.
Files changed (120) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +217 -48
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/components.js +4 -2
  8. package/dist/src/configured-declarations.js +56 -7
  9. package/dist/src/conformance-record.js +2 -2
  10. package/dist/src/dependencies.js +34 -17
  11. package/dist/src/deprecation.js +35 -24
  12. package/dist/src/destructuring.js +37 -29
  13. package/dist/src/discover.js +105 -28
  14. package/dist/src/edges.js +4 -1
  15. package/dist/src/entry-points.js +7 -0
  16. package/dist/src/enum-group.js +46 -40
  17. package/dist/src/exempt.js +42 -1
  18. package/dist/src/findings/completion.js +37 -8
  19. package/dist/src/findings/emitters.js +20 -6
  20. package/dist/src/findings/interfaces.js +7 -1
  21. package/dist/src/findings/intra-function.js +91 -2
  22. package/dist/src/findings/self-check.js +30 -7
  23. package/dist/src/findings/unused-declarations.js +15 -3
  24. package/dist/src/findings-pass.js +18 -11
  25. package/dist/src/framework-lifecycle.js +8 -6
  26. package/dist/src/implementations.js +45 -23
  27. package/dist/src/interface-satisfaction.js +30 -27
  28. package/dist/src/intra-function-parts.js +660 -0
  29. package/dist/src/inventory.js +85 -4
  30. package/dist/src/json-chunks.js +76 -0
  31. package/dist/src/kinds.js +337 -35
  32. package/dist/src/partial.js +23 -0
  33. package/dist/src/query.js +207 -0
  34. package/dist/src/references.js +92 -20
  35. package/dist/src/reflective-lookup.js +46 -4
  36. package/dist/src/report.js +14 -3
  37. package/dist/src/reporters/annotations.js +9 -5
  38. package/dist/src/reporters/json.js +3 -2
  39. package/dist/src/reporters/sarif.js +22 -16
  40. package/dist/src/reporters/template.js +112 -12
  41. package/dist/src/reporters/text.js +8 -6
  42. package/dist/src/roots.js +27 -11
  43. package/dist/src/serialization-contract.js +53 -32
  44. package/dist/src/session.js +79 -39
  45. package/dist/src/signatures.js +32 -0
  46. package/dist/src/suppress-file.js +29 -7
  47. package/dist/src/suppress-inline.js +8 -1
  48. package/dist/src/suppress.js +27 -0
  49. package/dist/src/sweep.js +4 -1
  50. package/dist/src/value-reach.js +44 -25
  51. package/dist/src/verbs/analyze.js +39 -14
  52. package/dist/src/verbs/describe.js +12 -8
  53. package/dist/src/verbs/explain.js +22 -9
  54. package/dist/src/verbs/print-projects.js +40 -7
  55. package/dist/src/verbs/print-retained.js +6 -2
  56. package/dist/src/verbs/print-roots.js +5 -1
  57. package/dist/src/version.js +1 -1
  58. package/jsr.json +1 -1
  59. package/package.json +3 -3
  60. package/src/alias-chain.ts +75 -11
  61. package/src/analysis.ts +385 -104
  62. package/src/calls.ts +10 -9
  63. package/src/class-members.ts +10 -11
  64. package/src/components.ts +4 -2
  65. package/src/configured-declarations.ts +80 -8
  66. package/src/conformance-record.ts +2 -2
  67. package/src/dependencies.ts +42 -20
  68. package/src/deprecation.ts +53 -27
  69. package/src/destructuring.ts +50 -39
  70. package/src/discover.ts +177 -33
  71. package/src/edges.ts +4 -1
  72. package/src/entry-points.ts +7 -0
  73. package/src/enum-group.ts +56 -42
  74. package/src/exempt.ts +50 -2
  75. package/src/finding.ts +5 -1
  76. package/src/findings/completion.ts +46 -10
  77. package/src/findings/emitter.ts +3 -0
  78. package/src/findings/emitters.ts +27 -6
  79. package/src/findings/interfaces.ts +7 -1
  80. package/src/findings/intra-function.ts +106 -3
  81. package/src/findings/self-check.ts +48 -7
  82. package/src/findings/unused-declarations.ts +18 -4
  83. package/src/findings-pass.ts +26 -11
  84. package/src/framework-lifecycle.ts +10 -6
  85. package/src/host.ts +5 -3
  86. package/src/implementations.ts +59 -23
  87. package/src/interface-satisfaction.ts +30 -27
  88. package/src/intra-function-parts.ts +889 -0
  89. package/src/inventory.ts +97 -4
  90. package/src/json-chunks.ts +87 -0
  91. package/src/kinds.ts +355 -41
  92. package/src/partial.ts +40 -0
  93. package/src/query.ts +522 -0
  94. package/src/references.ts +119 -38
  95. package/src/reflective-lookup.ts +56 -6
  96. package/src/report.ts +32 -4
  97. package/src/reporters/annotations.ts +23 -21
  98. package/src/reporters/json.ts +3 -2
  99. package/src/reporters/reporter.ts +2 -2
  100. package/src/reporters/sarif.ts +27 -18
  101. package/src/reporters/template.ts +119 -14
  102. package/src/reporters/text.ts +12 -12
  103. package/src/roots.ts +34 -12
  104. package/src/serialization-contract.ts +59 -40
  105. package/src/session.ts +146 -58
  106. package/src/signatures.ts +55 -0
  107. package/src/suppress-file.ts +40 -8
  108. package/src/suppress-inline.ts +8 -1
  109. package/src/suppress.ts +31 -0
  110. package/src/sweep.ts +9 -4
  111. package/src/value-reach.ts +66 -28
  112. package/src/verbs/analyze.ts +51 -14
  113. package/src/verbs/describe.ts +13 -9
  114. package/src/verbs/explain.ts +20 -9
  115. package/src/verbs/print-projects.ts +53 -7
  116. package/src/verbs/print-retained.ts +6 -2
  117. package/src/verbs/print-roots.ts +5 -1
  118. package/src/version.ts +1 -1
  119. package/dist/src/reporters/rules.js +0 -178
  120. package/src/reporters/rules.ts +0 -195
@@ -1,7 +1,7 @@
1
1
  import type { Finding } from "../finding.ts";
2
2
  import type { InventorySymbol, SymbolKind } from "../inventory.ts";
3
3
  import type { MatrixCandidate } from "../matrix.ts";
4
- import type { Emitter } from "./emitter.ts";
4
+ import type { Emitter, EmitterInput } from "./emitter.ts";
5
5
  import { unreachableExports } from "./visibility-narrowing.ts";
6
6
 
7
7
  /** The kinds of declaration that are members of a class, an interface, a type or an enum. */
@@ -137,7 +137,17 @@ function messageOf(code: string, judged: Judged): string {
137
137
  }
138
138
 
139
139
  /** The findings of the unused-declarations family, `DS1000` to `DS1099`. */
140
- export const unusedDeclarations: Emitter = (input) => {
140
+ export const unusedDeclarations: Emitter = (input) => familyFindings(input, false);
141
+
142
+ /**
143
+ * The finding each member of a dead container would carry were its container live: what
144
+ * a record on such a member is matched against, since the report carries the container's
145
+ * finding rather than one of the member's own.
146
+ */
147
+ export const fallenMembers: Emitter = (input) => familyFindings(input, true);
148
+
149
+ /** The family's findings, or, where `fallen` is set, its dead containers' members' alone. */
150
+ function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
141
151
  const union = input.swept.matrix.union;
142
152
  const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
143
153
  const unreachable = unreachableExports(input);
@@ -149,11 +159,15 @@ export const unusedDeclarations: Emitter = (input) => {
149
159
  if (symbol === undefined) {
150
160
  continue;
151
161
  }
162
+ const containerDead = dead.has(symbol.parent);
163
+ if (fallen !== containerDead) {
164
+ continue;
165
+ }
152
166
  const judged: Judged = {
153
167
  candidate,
154
168
  symbol,
155
169
  test: union.test[at] === true,
156
- containerDead: dead.has(symbol.parent),
170
+ containerDead: containerDead && !fallen,
157
171
  deprecated: input.deprecated.has(symbol.id),
158
172
  unreachable: unreachable.has(symbol.id),
159
173
  };
@@ -175,4 +189,4 @@ export const unusedDeclarations: Emitter = (input) => {
175
189
  });
176
190
  }
177
191
  return found;
178
- };
192
+ }
@@ -122,19 +122,34 @@ export function verdictOf(result: PassResult, config: Config): number {
122
122
  return failing ? EXIT_FINDINGS : EXIT_CLEAN;
123
123
  }
124
124
 
125
+ /** What a baseline row is recorded from: a finding's code, subject and file. */
126
+ export interface RecordableFinding {
127
+ readonly code: string;
128
+ readonly symbol: { readonly ref: string; readonly kind: string };
129
+ readonly position: { readonly path: string };
130
+ }
131
+
125
132
  /**
126
- * The findings of a report as a baseline records them, in report order. A finding about a
127
- * row of a document is not recorded: no record withholds one, so a row recording it would
128
- * be stale on the first read back.
133
+ * The rows one round of a baseline write records from its findings, in their order. A
134
+ * finding about a row of a document is not recorded: no record withholds one, so a row
135
+ * recording it would be stale on the first read back. Two findings that would record the
136
+ * same row, two parts of one declaration under one code, record it once.
129
137
  */
130
- export function recordedFindings(result: PassResult): readonly Recorded[] {
131
- return result.findings
132
- .filter((finding) => !isRowSubject(finding.symbol.kind))
133
- .map((finding) => ({
134
- code: finding.code,
135
- symbol: finding.symbol.ref,
136
- path: finding.position.path,
137
- }));
138
+ export function recordedFindings(findings: readonly RecordableFinding[]): readonly Recorded[] {
139
+ const rows = new Map<string, Recorded>();
140
+ for (const finding of findings) {
141
+ if (isRowSubject(finding.symbol.kind)) {
142
+ continue;
143
+ }
144
+ const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
145
+ rows.set(rowKey(row), rows.get(rowKey(row)) ?? row);
146
+ }
147
+ return [...rows.values()];
148
+ }
149
+
150
+ /** One row's identity: its code, its symbol and its path. */
151
+ export function rowKey(row: Recorded): string {
152
+ return `${row.code}\u0000${row.symbol}\u0000${row.path}`;
138
153
  }
139
154
 
140
155
  /** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
@@ -30,6 +30,7 @@ import {
30
30
  import type { DetectorInput, Evidence } from "./exempt.ts";
31
31
  import { nodeKey, type Inventory } from "./inventory.ts";
32
32
  import { renderPosition } from "./position.ts";
33
+ import { must } from "./query.ts";
33
34
  import type { ProjectView } from "./session.ts";
34
35
 
35
36
  /** One class that is a component of one contract, and what says it is. */
@@ -182,7 +183,7 @@ function byBases<Brand>(
182
183
  if (classes.length === 0) {
183
184
  return [];
184
185
  }
185
- const checker = project.checker;
186
+ const queries = project.queries;
186
187
  const ancestry = new Map<number, readonly Type[]>();
187
188
  const ancestorsOf = (type: Type, seen: Set<number>): readonly Type[] => {
188
189
  const known = ancestry.get(type.id);
@@ -192,15 +193,16 @@ function byBases<Brand>(
192
193
  const declared = type.isClassOrInterface()
193
194
  ? type
194
195
  : type.isTypeReference()
195
- ? type.getTarget()
196
+ ? must(queries.targetOf(type))
196
197
  : undefined;
197
198
  if (declared === undefined || !declared.isClassOrInterface() || seen.has(declared.id)) {
198
199
  return [];
199
200
  }
200
201
  seen.add(declared.id);
201
- const found = checker
202
- .getBaseTypes(declared)
203
- .flatMap((base) => [base, ...ancestorsOf(base, seen)]);
202
+ const found = must(queries.baseTypes(declared)).flatMap((base) => [
203
+ base,
204
+ ...ancestorsOf(base, seen),
205
+ ]);
204
206
  ancestry.set(type.id, found);
205
207
  return found;
206
208
  };
@@ -215,7 +217,9 @@ function byBases<Brand>(
215
217
  if (type === undefined) {
216
218
  return;
217
219
  }
218
- const ancestors = ancestorsOf(type, new Set()).flatMap((base) => base.getSymbol() ?? []);
220
+ const ancestors = ancestorsOf(type, new Set()).flatMap(
221
+ (base) => must(queries.symbolOfType(base)) ?? [],
222
+ );
219
223
  for (const contract of based) {
220
224
  for (const entry of contract.bases) {
221
225
  if (ancestors.some((symbol) => names(entry, symbol, chains))) {
package/src/host.ts CHANGED
@@ -35,8 +35,10 @@ export interface Host {
35
35
  */
36
36
  analyzerVersion(): string;
37
37
  /**
38
- * Writes one document whole, or a throw naming why it cannot. The write is atomic: a
39
- * reader of the path sees the previous document or this one and never a part of it.
38
+ * Writes one document from its pieces, in order, or a throw naming why it cannot. The
39
+ * pieces are written as they come, so the document is never held as one string. The
40
+ * write is atomic: a reader of the path sees the previous document or this one and
41
+ * never a part of it.
40
42
  */
41
- writeFile(path: string, text: string): void;
43
+ writeDocument(path: string, pieces: Iterable<string>): void;
42
44
  }
@@ -17,7 +17,7 @@ import {
17
17
  import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/unstable/sync";
18
18
  import { aliasChains } from "./alias-chain.ts";
19
19
  import { nodeKey, type Inventory } from "./inventory.ts";
20
- import { DEFAULT_BATCH_CAP } from "./references.ts";
20
+ import { must, Unanswerable } from "./query.ts";
21
21
  import type { ProjectView } from "./session.ts";
22
22
 
23
23
  /** What one run knows about how its interfaces are implemented. */
@@ -29,6 +29,13 @@ export interface Implementations {
29
29
  * class's implementation is a method whose body holds no statement.
30
30
  */
31
31
  readonly bodies: ReadonlyMap<string, ReadonlyMap<string, boolean>>;
32
+ /**
33
+ * The interface methods whose implementations a project could not read in full,
34
+ * because the checker left a question about them unanswered. Each is treated as a
35
+ * marker, whose every implementation is empty, so none is reported for want of an
36
+ * answer.
37
+ */
38
+ readonly unknown: ReadonlySet<string>;
32
39
  }
33
40
 
34
41
  /** One interface or class the inventory holds, by its declaration. */
@@ -97,24 +104,22 @@ function walk<Brand>(
97
104
  return { interfaces, classes, clauses };
98
105
  }
99
106
 
100
- /** The type at each node, asked in capped batches. */
107
+ /** The type at each node. */
101
108
  function typesAt<Brand>(project: ProjectView<Brand>, nodes: readonly Node[]): Map<Node, Type> {
102
109
  const typeAt = new Map<Node, Type>();
103
- for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
104
- const run = nodes.slice(from, from + DEFAULT_BATCH_CAP);
105
- project.checker.getTypeAtLocation(run).forEach((type, index) => {
106
- const node = run[index];
107
- if (type !== undefined && node !== undefined) {
108
- typeAt.set(node, type);
109
- }
110
- });
111
- }
110
+ project.queries.typesAt(nodes).forEach((answer, index) => {
111
+ const type = must(answer);
112
+ const node = nodes[index];
113
+ if (type !== undefined && node !== undefined) {
114
+ typeAt.set(node, type);
115
+ }
116
+ });
112
117
  return typeAt;
113
118
  }
114
119
 
115
120
  /** Whether one class member is a method whose every written body holds no statement. */
116
121
  function emptyImplementation<Brand>(project: ProjectView<Brand>, member: TSSymbol): boolean {
117
- const own = new Set(project.ownSourceFiles().map((file) => file.fileName));
122
+ const own = project.ownPaths();
118
123
  let bodies = 0;
119
124
  for (const handle of member.declarations) {
120
125
  // A body outside the target is one this analysis cannot read, so it is not empty.
@@ -135,19 +140,41 @@ function emptyImplementation<Brand>(project: ProjectView<Brand>, member: TSSymbo
135
140
  return bodies > 0;
136
141
  }
137
142
 
138
- /** How one project's interfaces are implemented by its classes. */
143
+ /**
144
+ * How one project's interfaces are implemented by its classes. Where the checker leaves
145
+ * a question unanswered, what was read stands and every interface method of the project
146
+ * is named in {@link Implementations.unknown}.
147
+ */
139
148
  export function implementations<Brand>(
140
149
  project: ProjectView<Brand>,
141
150
  held: Inventory,
142
151
  ): Implementations {
143
152
  const classes = new Map<string, Set<string>>();
144
153
  const bodies = new Map<string, Map<string, boolean>>();
154
+ try {
155
+ implementedIn(project, held, classes, bodies);
156
+ return { classes, bodies, unknown: new Set() };
157
+ } catch (error: unknown) {
158
+ if (!(error instanceof Unanswerable)) {
159
+ throw error;
160
+ }
161
+ const methods = held.symbols.filter((symbol) => symbol.kind === "interface-method");
162
+ return { classes, bodies, unknown: new Set(methods.map((symbol) => symbol.id)) };
163
+ }
164
+ }
165
+
166
+ function implementedIn<Brand>(
167
+ project: ProjectView<Brand>,
168
+ held: Inventory,
169
+ classes: Map<string, Set<string>>,
170
+ bodies: Map<string, Map<string, boolean>>,
171
+ ): void {
145
172
  const walked = walk(project, held);
146
173
  if (walked.interfaces.length === 0 || walked.classes.length === 0) {
147
- return { classes, bodies };
174
+ return;
148
175
  }
149
176
 
150
- const checker = project.checker;
177
+ const queries = project.queries;
151
178
  const typeAt = typesAt(project, [
152
179
  ...walked.interfaces.map((one) => one.nameNode),
153
180
  ...walked.classes.map((one) => one.nameNode),
@@ -178,10 +205,10 @@ export function implementations<Brand>(
178
205
  }
179
206
 
180
207
  const membersOf = (at: InterfaceAt): readonly TSSymbol[] =>
181
- (at.members ??= checker.getPropertiesOfType(at.type));
208
+ (at.members ??= must(queries.propertiesOf(at.type)));
182
209
  const classMembers = (at: ClassAt): ReadonlyMap<string, TSSymbol> =>
183
210
  (at.members ??= new Map(
184
- checker.getPropertiesOfType(at.type).map((member) => [member.escapedName, member]),
211
+ must(queries.propertiesOf(at.type)).map((member) => [member.escapedName, member]),
185
212
  ));
186
213
 
187
214
  const methodIds = new Set(
@@ -233,12 +260,14 @@ export function implementations<Brand>(
233
260
  if (entry === undefined || implementer === undefined) {
234
261
  continue;
235
262
  }
236
- const at = byType.get(entry.isTypeReference() ? entry.getTarget().id : entry.id);
263
+ const at = byType.get(must(queries.targetOf(entry)).id);
237
264
  if (at !== undefined) {
238
265
  pair(at, implementer);
239
266
  }
240
267
  }
241
268
 
269
+ // Every pair whose names line up is asked about at once.
270
+ const candidates: { readonly at: InterfaceAt; readonly implementer: ClassAt }[] = [];
242
271
  for (const at of byType.values()) {
243
272
  const members = at.generic ? [] : membersOf(at);
244
273
  if (members.length === 0) {
@@ -252,18 +281,25 @@ export function implementations<Brand>(
252
281
  const written = classMembers(implementer);
253
282
  if (
254
283
  required.every((member) => written.has(member.escapedName)) &&
255
- members.some((member) => written.has(member.escapedName)) &&
256
- checker.isTypeAssignableTo(implementer.type, at.type)
284
+ members.some((member) => written.has(member.escapedName))
257
285
  ) {
258
- pair(at, implementer);
286
+ candidates.push({ at, implementer });
259
287
  }
260
288
  }
261
289
  }
262
- return { classes, bodies };
290
+ const assignable = queries.assignableEach(
291
+ candidates.map((one) => ({ source: one.implementer.type, target: one.at.type })),
292
+ );
293
+ candidates.forEach((one, index) => {
294
+ if (must(assignable[index] ?? false)) {
295
+ pair(one.at, one.implementer);
296
+ }
297
+ });
263
298
  }
264
299
 
265
300
  /** Every project's answers as one run's, each implementation once. */
266
301
  export function mergeImplementations(per: readonly Implementations[]): Implementations {
302
+ const unknown = new Set(per.flatMap((one) => [...one.unknown]));
267
303
  const classes = new Map<string, Set<string>>();
268
304
  const bodies = new Map<string, Map<string, boolean>>();
269
305
  for (const one of per) {
@@ -282,5 +318,5 @@ export function mergeImplementations(per: readonly Implementations[]): Implement
282
318
  }
283
319
  }
284
320
  }
285
- return { classes, bodies };
321
+ return { classes, bodies, unknown };
286
322
  }
@@ -52,7 +52,7 @@ import { aliasChains } from "./alias-chain.ts";
52
52
  import type { DetectorInput, Evidence } from "./exempt.ts";
53
53
  import { nodeKey } from "./inventory.ts";
54
54
  import { renderPosition } from "./position.ts";
55
- import { DEFAULT_BATCH_CAP } from "./references.ts";
55
+ import { must } from "./query.ts";
56
56
 
57
57
  /** One class the inventory holds, by its declaration. */
58
58
  interface ClassAt {
@@ -246,7 +246,7 @@ function walk(files: readonly SourceFile[], declarations: ReadonlyMap<string, st
246
246
  */
247
247
  export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
248
248
  const { project, held, targetRoot } = input;
249
- const checker = project.checker;
249
+ const queries = project.queries;
250
250
  const files = project.ownSourceFiles();
251
251
  const { classes, clauses, flows } = walk(files, held.declarations);
252
252
  if (classes.length === 0) {
@@ -261,15 +261,13 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
261
261
  ...flows.filter((flow) => flow.self === undefined).map((flow) => flow.value),
262
262
  ];
263
263
  const typeAt = new Map<Node, Type>();
264
- for (let from = 0; from < asked.length; from += DEFAULT_BATCH_CAP) {
265
- const run = asked.slice(from, from + DEFAULT_BATCH_CAP);
266
- checker.getTypeAtLocation(run).forEach((type, index) => {
267
- const node = run[index];
268
- if (type !== undefined && node !== undefined) {
269
- typeAt.set(node, type);
270
- }
271
- });
272
- }
264
+ queries.typesAt(asked).forEach((answer, index) => {
265
+ const type = must(answer);
266
+ const node = asked[index];
267
+ if (type !== undefined && node !== undefined) {
268
+ typeAt.set(node, type);
269
+ }
270
+ });
273
271
 
274
272
  const classOfType = new Map<number, ClassAt>();
275
273
  const instanceOf = new Map<string, Type>();
@@ -289,7 +287,7 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
289
287
  }
290
288
  let parts = constituents.get(type.id);
291
289
  if (parts === undefined) {
292
- parts = type.getTypes();
290
+ parts = must(queries.constituents(type));
293
291
  constituents.set(type.id, parts);
294
292
  }
295
293
  return parts;
@@ -307,7 +305,7 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
307
305
  return [];
308
306
  }
309
307
  if (!targets.has(part.id)) {
310
- targets.set(part.id, classOfType.get(part.getTarget().id));
308
+ targets.set(part.id, classOfType.get(must(queries.targetOf(part)).id));
311
309
  }
312
310
  const generic = targets.get(part.id);
313
311
  return generic === undefined ? [] : [{ owner: generic, type: part }];
@@ -318,7 +316,7 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
318
316
  const interfacesOf = (type: Type): readonly { type: Type; name: string }[] =>
319
317
  partsOf(type).flatMap((part) => {
320
318
  if (!interfaceNames.has(part.id)) {
321
- const symbol = part.isObjectType() ? part.getSymbol() : undefined;
319
+ const symbol = part.isObjectType() ? must(queries.symbolOfType(part)) : undefined;
322
320
  interfaceNames.set(
323
321
  part.id,
324
322
  symbol !== undefined && (symbol.flags & SymbolFlags.Interface) !== 0
@@ -352,7 +350,8 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
352
350
  }
353
351
  }
354
352
 
355
- for (const flow of flows) {
353
+ // The positions of every flow carrying a class are asked for at once.
354
+ const carrying = flows.flatMap((flow) => {
356
355
  const self = flow.self === undefined ? undefined : instanceOf.get(flow.self.id);
357
356
  const valueType = typeAt.get(flow.value);
358
357
  const sources =
@@ -361,32 +360,35 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
361
360
  : valueType === undefined
362
361
  ? []
363
362
  : classesOf(valueType);
364
- if (sources.length === 0) {
365
- continue;
366
- }
367
- const position = checker.getContextualType(flow.value);
363
+ return sources.length === 0 ? [] : [{ flow, sources }];
364
+ });
365
+ const positions = queries.contextualTypes(carrying.map((one) => one.flow.value));
366
+ carrying.forEach(({ flow, sources }, index) => {
367
+ const position = must(positions[index]);
368
368
  if (position === undefined) {
369
- continue;
369
+ return;
370
370
  }
371
371
  for (const reached of interfacesOf(position)) {
372
372
  for (const source of sources) {
373
373
  reach(source.owner, source.type, reached.type, reached.name, flow.value);
374
374
  }
375
375
  }
376
- }
376
+ });
377
377
 
378
378
  const chains = aliasChains(project, held);
379
379
  const required = new Map<number, ReadonlySet<string>>();
380
380
  const members = new Map<string, ReadonlyMap<string, readonly string[]>>();
381
381
  const found: Evidence[] = [];
382
- for (const pair of pairs.values()) {
383
- if (!checker.isTypeAssignableTo(pair.source, pair.target)) {
382
+ const asking = [...pairs.values()];
383
+ const assignable = queries.assignableEach(asking);
384
+ for (const [index, pair] of asking.entries()) {
385
+ if (!must(assignable[index] ?? false)) {
384
386
  continue;
385
387
  }
386
388
  let names = required.get(pair.target.id);
387
389
  if (names === undefined) {
388
390
  names = new Set(
389
- checker.getPropertiesOfType(pair.target).map((property) => property.escapedName),
391
+ must(queries.propertiesOf(pair.target)).map((property) => property.escapedName),
390
392
  );
391
393
  required.set(pair.target.id, names);
392
394
  }
@@ -394,9 +396,10 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
394
396
  if (answering === undefined) {
395
397
  const instance = instanceOf.get(pair.owner.id) ?? pair.source;
396
398
  answering = new Map(
397
- checker
398
- .getPropertiesOfType(instance)
399
- .map((property) => [property.escapedName, chains.declarationsOf(property)]),
399
+ must(queries.propertiesOf(instance)).map((property) => [
400
+ property.escapedName,
401
+ chains.declarationsOf(property),
402
+ ]),
400
403
  );
401
404
  members.set(pair.owner.id, answering);
402
405
  }