@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.
Files changed (88) hide show
  1. package/README.md +3 -3
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +119 -23
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/configured-declarations.js +22 -9
  8. package/dist/src/dependencies.js +34 -17
  9. package/dist/src/deprecation.js +35 -24
  10. package/dist/src/destructuring.js +37 -29
  11. package/dist/src/discover.js +105 -28
  12. package/dist/src/entry-points.js +7 -0
  13. package/dist/src/enum-group.js +46 -40
  14. package/dist/src/exempt.js +42 -1
  15. package/dist/src/findings/emitters.js +15 -1
  16. package/dist/src/findings/interfaces.js +7 -1
  17. package/dist/src/framework-lifecycle.js +8 -6
  18. package/dist/src/implementations.js +45 -23
  19. package/dist/src/interface-satisfaction.js +30 -27
  20. package/dist/src/intra-function-parts.js +37 -20
  21. package/dist/src/inventory.js +85 -4
  22. package/dist/src/json-chunks.js +76 -0
  23. package/dist/src/partial.js +23 -0
  24. package/dist/src/query.js +207 -0
  25. package/dist/src/references.js +92 -20
  26. package/dist/src/reflective-lookup.js +18 -9
  27. package/dist/src/report.js +3 -1
  28. package/dist/src/reporters/annotations.js +9 -5
  29. package/dist/src/reporters/json.js +3 -2
  30. package/dist/src/reporters/sarif.js +2 -1
  31. package/dist/src/reporters/template.js +48 -7
  32. package/dist/src/reporters/text.js +8 -6
  33. package/dist/src/roots.js +27 -11
  34. package/dist/src/serialization-contract.js +12 -10
  35. package/dist/src/session.js +79 -39
  36. package/dist/src/suppress-file.js +2 -1
  37. package/dist/src/sweep.js +4 -1
  38. package/dist/src/value-reach.js +14 -19
  39. package/dist/src/verbs/analyze.js +20 -3
  40. package/dist/src/verbs/explain.js +22 -9
  41. package/dist/src/verbs/print-projects.js +40 -7
  42. package/dist/src/verbs/print-retained.js +6 -2
  43. package/dist/src/verbs/print-roots.js +5 -1
  44. package/jsr.json +1 -1
  45. package/package.json +3 -3
  46. package/src/alias-chain.ts +75 -11
  47. package/src/analysis.ts +223 -61
  48. package/src/calls.ts +10 -9
  49. package/src/class-members.ts +10 -11
  50. package/src/configured-declarations.ts +29 -13
  51. package/src/dependencies.ts +42 -20
  52. package/src/deprecation.ts +53 -27
  53. package/src/destructuring.ts +50 -39
  54. package/src/discover.ts +177 -33
  55. package/src/entry-points.ts +7 -0
  56. package/src/enum-group.ts +56 -42
  57. package/src/exempt.ts +50 -2
  58. package/src/findings/emitters.ts +18 -1
  59. package/src/findings/interfaces.ts +7 -1
  60. package/src/framework-lifecycle.ts +10 -6
  61. package/src/host.ts +5 -3
  62. package/src/implementations.ts +59 -23
  63. package/src/interface-satisfaction.ts +30 -27
  64. package/src/intra-function-parts.ts +43 -25
  65. package/src/inventory.ts +97 -4
  66. package/src/json-chunks.ts +87 -0
  67. package/src/partial.ts +40 -0
  68. package/src/query.ts +522 -0
  69. package/src/references.ts +119 -38
  70. package/src/reflective-lookup.ts +21 -11
  71. package/src/report.ts +14 -2
  72. package/src/reporters/annotations.ts +23 -21
  73. package/src/reporters/json.ts +3 -2
  74. package/src/reporters/reporter.ts +2 -2
  75. package/src/reporters/sarif.ts +3 -2
  76. package/src/reporters/template.ts +50 -8
  77. package/src/reporters/text.ts +12 -12
  78. package/src/roots.ts +34 -12
  79. package/src/serialization-contract.ts +12 -10
  80. package/src/session.ts +146 -58
  81. package/src/suppress-file.ts +6 -2
  82. package/src/sweep.ts +9 -4
  83. package/src/value-reach.ts +20 -23
  84. package/src/verbs/analyze.ts +25 -3
  85. package/src/verbs/explain.ts +20 -9
  86. package/src/verbs/print-projects.ts +53 -7
  87. package/src/verbs/print-retained.ts +6 -2
  88. package/src/verbs/print-roots.ts +5 -1
@@ -11,7 +11,7 @@ import { TypeFlags } from "@typescript/native/unstable/sync";
11
11
  import { aliasChains } from "./alias-chain.js";
12
12
  import { nodeKey } from "./inventory.js";
13
13
  import { renderPosition } from "./position.js";
14
- import { DEFAULT_BATCH_CAP } from "./references.js";
14
+ import { isAnswered, UNANSWERED } from "./query.js";
15
15
  /** The operand types a conversion produces an enum value from. */
16
16
  const NUMBER = TypeFlags.NumberLike;
17
17
  const STRING = TypeFlags.StringLike;
@@ -104,24 +104,31 @@ function sitesOf(file) {
104
104
  * union, and itself otherwise. An enum's own value, or one of its members, is a value the
105
105
  * source names, whichever number or string it also is.
106
106
  */
107
- function partsOf(type) {
108
- const parts = type.isUnionType() ? type.getTypes() : [type];
109
- return parts.filter((part) => (part.flags & TypeFlags.EnumLike) === 0);
107
+ function partsOf(project, type) {
108
+ const parts = type.isUnionType() ? project.queries.constituents(type) : [type];
109
+ return isAnswered(parts)
110
+ ? parts.filter((part) => (part.flags & TypeFlags.EnumLike) === 0)
111
+ : parts;
110
112
  }
113
+ /** The detail a site records whose operand's type the checker could not answer. */
114
+ const UNTYPED_DETAIL = "converted from a value of unanswered type";
111
115
  /**
112
116
  * The detail one site records about the enum it produces, or undefined where the
113
117
  * operand's type converts nothing: a number, or a string an assertion converts, is
114
118
  * converted, a value of type `any` or `unknown` is decoded, and a value of an enum is one
115
119
  * the source names.
116
120
  */
117
- function detailOf(site, enumName, operand) {
121
+ function detailOf(project, site, enumName, operand) {
118
122
  if (site.form === "lookup") {
119
123
  return `looked up by an element access on ${enumName}`;
120
124
  }
121
125
  if (operand === undefined) {
122
126
  return undefined;
123
127
  }
124
- const parts = partsOf(operand);
128
+ const parts = operand === UNANSWERED ? UNANSWERED : partsOf(project, operand);
129
+ if (parts === UNANSWERED) {
130
+ return UNTYPED_DETAIL;
131
+ }
125
132
  if (parts.some((part) => (part.flags & NUMBER) !== 0)) {
126
133
  return "converted from number";
127
134
  }
@@ -160,32 +167,33 @@ export function enumGroup(input) {
160
167
  const sites = files.flatMap(sitesOf);
161
168
  const named = new Map();
162
169
  const chains = aliasChains(project, held);
163
- for (let from = 0; from < sites.length; from += DEFAULT_BATCH_CAP) {
164
- const run = sites.slice(from, from + DEFAULT_BATCH_CAP);
165
- project.symbolsAt(run.map((site) => project.handle(site.name))).forEach((symbol, index) => {
166
- const site = run[index];
167
- if (symbol === undefined || site === undefined) {
168
- return;
169
- }
170
- const target = chains.chainOf(symbol).find((link) => enums.has(link.id));
171
- if (target !== undefined) {
172
- named.set(site, target.id);
170
+ // A name the checker could not resolve may stand for any enum of the target it spells.
171
+ const enumsNamed = (text) => [...enums].flatMap(([id, name]) => (name === text ? [id] : []));
172
+ project.symbolsAt(sites.map((site) => project.handle(site.name))).forEach((symbol, index) => {
173
+ const site = sites[index];
174
+ if (symbol === undefined || site === undefined) {
175
+ return;
176
+ }
177
+ if (symbol === UNANSWERED) {
178
+ const ids = enumsNamed(site.name.getText());
179
+ if (ids.length > 0) {
180
+ named.set(site, ids);
173
181
  }
174
- });
175
- }
182
+ return;
183
+ }
184
+ const target = chains.chainOf(symbol).find((link) => enums.has(link.id));
185
+ if (target !== undefined) {
186
+ named.set(site, [target.id]);
187
+ }
188
+ });
176
189
  const typed = sites.filter((site) => named.has(site) && site.operand !== undefined);
177
190
  const operandType = new Map();
178
- for (let from = 0; from < typed.length; from += DEFAULT_BATCH_CAP) {
179
- const run = typed.slice(from, from + DEFAULT_BATCH_CAP);
180
- project.checker
181
- .getTypeAtLocation(run.map((site) => site.operand ?? site.at))
182
- .forEach((type, index) => {
183
- const site = run[index];
184
- if (type !== undefined && site !== undefined) {
185
- operandType.set(site, type);
186
- }
187
- });
188
- }
191
+ project.queries.typesAt(typed.map((site) => site.operand ?? site.at)).forEach((type, index) => {
192
+ const site = typed[index];
193
+ if (type !== undefined && site !== undefined) {
194
+ operandType.set(site, type);
195
+ }
196
+ });
189
197
  const members = new Map();
190
198
  for (const symbol of held.symbols) {
191
199
  if (symbol.kind === "enum-member" && enums.has(symbol.parent)) {
@@ -200,17 +208,15 @@ export function enumGroup(input) {
200
208
  }
201
209
  const found = [];
202
210
  for (const site of sites) {
203
- const enumId = named.get(site);
204
- if (enumId === undefined) {
205
- continue;
206
- }
207
- const detail = detailOf(site, enums.get(enumId) ?? "", operandType.get(site));
208
- if (detail === undefined) {
209
- continue;
210
- }
211
- const position = renderPosition(site.at.getSourceFile(), targetRoot, site.at.getStart());
212
- for (const id of members.get(enumId) ?? []) {
213
- found.push({ id, detail, site: position });
211
+ for (const enumId of named.get(site) ?? []) {
212
+ const detail = detailOf(project, site, enums.get(enumId) ?? "", operandType.get(site));
213
+ if (detail === undefined) {
214
+ continue;
215
+ }
216
+ const position = renderPosition(site.at.getSourceFile(), targetRoot, site.at.getStart());
217
+ for (const id of members.get(enumId) ?? []) {
218
+ found.push({ id, detail, site: position });
219
+ }
214
220
  }
215
221
  }
216
222
  return found;
@@ -9,6 +9,7 @@
9
9
  import { ConfigError } from "./config.js";
10
10
  import { EXEMPTION_CLASSES, isExemptionClass, TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
11
11
  import { byPosition, positionKey } from "./position.js";
12
+ import { Unanswerable } from "./query.js";
12
13
  import { sweep } from "./sweep.js";
13
14
  /**
14
15
  * The classes `exemptions.disabled` switches off. A name is refused unless the
@@ -39,6 +40,46 @@ function retainable(allowed, visibility) {
39
40
  }
40
41
  return true;
41
42
  }
43
+ /**
44
+ * The kinds of declaration each class can hold back, which a detector that stops holds
45
+ * back whole. A class with no row here holds back every declaration but a file.
46
+ */
47
+ const RETAINABLE = new Map([
48
+ ["interface-satisfaction", new Set(["method", "class-member"])],
49
+ ["enum-group", new Set(["enum-member"])],
50
+ ["injection-container", new Set(["method", "class-member"])],
51
+ ["framework-lifecycle", new Set(["method", "class-member"])],
52
+ [
53
+ "serialization-contract",
54
+ new Set(["method", "class-member", "interface-method", "type-member"]),
55
+ ],
56
+ ]);
57
+ /** The detail of a record a detector that could not finish holds. */
58
+ const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
59
+ /** Whether one record is held by a detector that could not finish. */
60
+ export function isUnansweredRecord(record) {
61
+ return record.detail === UNANSWERED_DETAIL;
62
+ }
63
+ /**
64
+ * What one detector holds back over one project. A detector that stops because the
65
+ * checker left a question it needs unanswered holds back every declaration of the
66
+ * project its class could hold back, each recorded at its own position, so the gap
67
+ * never yields a finding.
68
+ */
69
+ function detected(exemptionClass, detect, input) {
70
+ try {
71
+ return detect(input);
72
+ }
73
+ catch (error) {
74
+ if (!(error instanceof Unanswerable)) {
75
+ throw error;
76
+ }
77
+ const kinds = RETAINABLE.get(exemptionClass);
78
+ return input.held.symbols
79
+ .filter((symbol) => (kinds === undefined ? symbol.kind !== "file" : kinds.has(symbol.kind)))
80
+ .map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
81
+ }
82
+ }
42
83
  /**
43
84
  * The records one project's detectors found, each class in the vocabulary's order and
44
85
  * none of a switched-off class. Evidence on a private member the class's row says it
@@ -55,7 +96,7 @@ export function computeExemptions(input, detectors, holding) {
55
96
  continue;
56
97
  }
57
98
  const allowed = typescriptVisibilityOf(exemptionClass);
58
- for (const evidence of detect(input)) {
99
+ for (const evidence of detected(exemptionClass, detect, input)) {
59
100
  if (!retainable(allowed, visibility.get(evidence.id))) {
60
101
  continue;
61
102
  }
@@ -20,6 +20,19 @@ export const EMITTERS = new Map([
20
20
  ["self-check", selfCheck],
21
21
  ["intra-function", intraFunction],
22
22
  ]);
23
+ /**
24
+ * The references of every declaration an unanswered question could have kept live, which
25
+ * the run reports nothing about: a finding about one would be computed from the gap.
26
+ */
27
+ function heldRefs(input) {
28
+ const held = new Set(input.swept.heldByUnanswered);
29
+ if (held.size === 0) {
30
+ return held;
31
+ }
32
+ return new Set(input.swept.matrix.union.symbols
33
+ .filter((symbol) => held.has(symbol.id))
34
+ .map((symbol) => symbol.ref));
35
+ }
23
36
  /** Every family's findings over one sweep, and those of any further emitter, completed by one step. */
24
37
  function completedOver(input, ...more) {
25
38
  return completed(input, [...EMITTERS.values(), ...more].flatMap((emit) => emit(input)));
@@ -50,7 +63,8 @@ export function decidedFindings(input, suppressed = { unmarked: input, records:
50
63
  const unmarked = suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked, fallenMembers);
51
64
  const would = [...unmarked, ...found];
52
65
  const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
53
- const kept = reportable(input, found.filter((finding) => !ledger.withheld(finding)));
66
+ const unanswered = heldRefs(input);
67
+ const kept = reportable(input, found.filter((finding) => !ledger.withheld(finding) && !unanswered.has(finding.symbol.ref)));
54
68
  const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
55
69
  return {
56
70
  findings: evaluated.findings,
@@ -88,8 +88,14 @@ function unusedMessage(candidate) {
88
88
  ? UNUSED_TEST_MESSAGE
89
89
  : UNUSED_MESSAGE;
90
90
  }
91
- /** Whether one interface method has implementations and every one has an empty body. */
91
+ /**
92
+ * Whether one interface method has implementations and every one has an empty body, or
93
+ * is one whose implementations the run could not read in full.
94
+ */
92
95
  function marker(input, method) {
96
+ if (input.implementations.unknown.has(method)) {
97
+ return true;
98
+ }
93
99
  const bodies = [...(input.implementations.bodies.get(method)?.values() ?? [])];
94
100
  return bodies.length > 0 && bodies.every((empty) => empty);
95
101
  }
@@ -12,6 +12,7 @@ import { classMembers, memberComponent, typesAt } from "./class-members.js";
12
12
  import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
13
13
  import { nodeKey } from "./inventory.js";
14
14
  import { renderPosition } from "./position.js";
15
+ import { must } from "./query.js";
15
16
  /** Every contract's components and bases, resolved in one pass over the entries. */
16
17
  function resolveContracts(project, held, contracts) {
17
18
  const entries = contracts.flatMap((contract) => [
@@ -106,7 +107,7 @@ function byBases(project, held, chains, contracts) {
106
107
  if (classes.length === 0) {
107
108
  return [];
108
109
  }
109
- const checker = project.checker;
110
+ const queries = project.queries;
110
111
  const ancestry = new Map();
111
112
  const ancestorsOf = (type, seen) => {
112
113
  const known = ancestry.get(type.id);
@@ -116,15 +117,16 @@ function byBases(project, held, chains, contracts) {
116
117
  const declared = type.isClassOrInterface()
117
118
  ? type
118
119
  : type.isTypeReference()
119
- ? type.getTarget()
120
+ ? must(queries.targetOf(type))
120
121
  : undefined;
121
122
  if (declared === undefined || !declared.isClassOrInterface() || seen.has(declared.id)) {
122
123
  return [];
123
124
  }
124
125
  seen.add(declared.id);
125
- const found = checker
126
- .getBaseTypes(declared)
127
- .flatMap((base) => [base, ...ancestorsOf(base, seen)]);
126
+ const found = must(queries.baseTypes(declared)).flatMap((base) => [
127
+ base,
128
+ ...ancestorsOf(base, seen),
129
+ ]);
128
130
  ancestry.set(type.id, found);
129
131
  return found;
130
132
  };
@@ -135,7 +137,7 @@ function byBases(project, held, chains, contracts) {
135
137
  if (type === undefined) {
136
138
  return;
137
139
  }
138
- const ancestors = ancestorsOf(type, new Set()).flatMap((base) => base.getSymbol() ?? []);
140
+ const ancestors = ancestorsOf(type, new Set()).flatMap((base) => must(queries.symbolOfType(base)) ?? []);
139
141
  for (const contract of based) {
140
142
  for (const entry of contract.bases) {
141
143
  if (ancestors.some((symbol) => names(entry, symbol, chains))) {
@@ -9,7 +9,7 @@ import { isClassDeclaration, isHeritageClause, isInterfaceDeclaration, isMethodD
9
9
  import { SymbolFlags } from "@typescript/native/unstable/sync";
10
10
  import { aliasChains } from "./alias-chain.js";
11
11
  import { nodeKey } from "./inventory.js";
12
- import { DEFAULT_BATCH_CAP } from "./references.js";
12
+ import { must, Unanswerable } from "./query.js";
13
13
  /** The interfaces, the classes and the `implements` entries of one project's own files. */
14
14
  function walk(project, held) {
15
15
  const interfaces = [];
@@ -41,23 +41,21 @@ function walk(project, held) {
41
41
  }
42
42
  return { interfaces, classes, clauses };
43
43
  }
44
- /** The type at each node, asked in capped batches. */
44
+ /** The type at each node. */
45
45
  function typesAt(project, nodes) {
46
46
  const typeAt = new Map();
47
- for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
48
- const run = nodes.slice(from, from + DEFAULT_BATCH_CAP);
49
- project.checker.getTypeAtLocation(run).forEach((type, index) => {
50
- const node = run[index];
51
- if (type !== undefined && node !== undefined) {
52
- typeAt.set(node, type);
53
- }
54
- });
55
- }
47
+ project.queries.typesAt(nodes).forEach((answer, index) => {
48
+ const type = must(answer);
49
+ const node = nodes[index];
50
+ if (type !== undefined && node !== undefined) {
51
+ typeAt.set(node, type);
52
+ }
53
+ });
56
54
  return typeAt;
57
55
  }
58
56
  /** Whether one class member is a method whose every written body holds no statement. */
59
57
  function emptyImplementation(project, member) {
60
- const own = new Set(project.ownSourceFiles().map((file) => file.fileName));
58
+ const own = project.ownPaths();
61
59
  let bodies = 0;
62
60
  for (const handle of member.declarations) {
63
61
  // A body outside the target is one this analysis cannot read, so it is not empty.
@@ -77,15 +75,32 @@ function emptyImplementation(project, member) {
77
75
  }
78
76
  return bodies > 0;
79
77
  }
80
- /** How one project's interfaces are implemented by its classes. */
78
+ /**
79
+ * How one project's interfaces are implemented by its classes. Where the checker leaves
80
+ * a question unanswered, what was read stands and every interface method of the project
81
+ * is named in {@link Implementations.unknown}.
82
+ */
81
83
  export function implementations(project, held) {
82
84
  const classes = new Map();
83
85
  const bodies = new Map();
86
+ try {
87
+ implementedIn(project, held, classes, bodies);
88
+ return { classes, bodies, unknown: new Set() };
89
+ }
90
+ catch (error) {
91
+ if (!(error instanceof Unanswerable)) {
92
+ throw error;
93
+ }
94
+ const methods = held.symbols.filter((symbol) => symbol.kind === "interface-method");
95
+ return { classes, bodies, unknown: new Set(methods.map((symbol) => symbol.id)) };
96
+ }
97
+ }
98
+ function implementedIn(project, held, classes, bodies) {
84
99
  const walked = walk(project, held);
85
100
  if (walked.interfaces.length === 0 || walked.classes.length === 0) {
86
- return { classes, bodies };
101
+ return;
87
102
  }
88
- const checker = project.checker;
103
+ const queries = project.queries;
89
104
  const typeAt = typesAt(project, [
90
105
  ...walked.interfaces.map((one) => one.nameNode),
91
106
  ...walked.classes.map((one) => one.nameNode),
@@ -114,8 +129,8 @@ export function implementations(project, held) {
114
129
  classAt.set(owner.id, { owner, type });
115
130
  }
116
131
  }
117
- const membersOf = (at) => (at.members ??= checker.getPropertiesOfType(at.type));
118
- const classMembers = (at) => (at.members ??= new Map(checker.getPropertiesOfType(at.type).map((member) => [member.escapedName, member])));
132
+ const membersOf = (at) => (at.members ??= must(queries.propertiesOf(at.type)));
133
+ const classMembers = (at) => (at.members ??= new Map(must(queries.propertiesOf(at.type)).map((member) => [member.escapedName, member])));
119
134
  const methodIds = new Set(held.symbols.filter((symbol) => symbol.kind === "interface-method").map((symbol) => symbol.id));
120
135
  const chains = aliasChains(project, held);
121
136
  const record = (at, implementer) => {
@@ -161,11 +176,13 @@ export function implementations(project, held) {
161
176
  if (entry === undefined || implementer === undefined) {
162
177
  continue;
163
178
  }
164
- const at = byType.get(entry.isTypeReference() ? entry.getTarget().id : entry.id);
179
+ const at = byType.get(must(queries.targetOf(entry)).id);
165
180
  if (at !== undefined) {
166
181
  pair(at, implementer);
167
182
  }
168
183
  }
184
+ // Every pair whose names line up is asked about at once.
185
+ const candidates = [];
169
186
  for (const at of byType.values()) {
170
187
  const members = at.generic ? [] : membersOf(at);
171
188
  if (members.length === 0) {
@@ -178,16 +195,21 @@ export function implementations(project, held) {
178
195
  }
179
196
  const written = classMembers(implementer);
180
197
  if (required.every((member) => written.has(member.escapedName)) &&
181
- members.some((member) => written.has(member.escapedName)) &&
182
- checker.isTypeAssignableTo(implementer.type, at.type)) {
183
- pair(at, implementer);
198
+ members.some((member) => written.has(member.escapedName))) {
199
+ candidates.push({ at, implementer });
184
200
  }
185
201
  }
186
202
  }
187
- return { classes, bodies };
203
+ const assignable = queries.assignableEach(candidates.map((one) => ({ source: one.implementer.type, target: one.at.type })));
204
+ candidates.forEach((one, index) => {
205
+ if (must(assignable[index] ?? false)) {
206
+ pair(one.at, one.implementer);
207
+ }
208
+ });
188
209
  }
189
210
  /** Every project's answers as one run's, each implementation once. */
190
211
  export function mergeImplementations(per) {
212
+ const unknown = new Set(per.flatMap((one) => [...one.unknown]));
191
213
  const classes = new Map();
192
214
  const bodies = new Map();
193
215
  for (const one of per) {
@@ -206,5 +228,5 @@ export function mergeImplementations(per) {
206
228
  }
207
229
  }
208
230
  }
209
- return { classes, bodies };
231
+ return { classes, bodies, unknown };
210
232
  }
@@ -17,7 +17,7 @@ import { SymbolFlags } from "@typescript/native/unstable/sync";
17
17
  import { aliasChains } from "./alias-chain.js";
18
18
  import { nodeKey } from "./inventory.js";
19
19
  import { renderPosition } from "./position.js";
20
- import { DEFAULT_BATCH_CAP } from "./references.js";
20
+ import { must } from "./query.js";
21
21
  /** The operators of an assignment whose right side is stored into the left side's type. */
22
22
  const STORES = new Set([
23
23
  SyntaxKind.EqualsToken,
@@ -157,7 +157,7 @@ function walk(files, declarations) {
157
157
  */
158
158
  export function interfaceSatisfaction(input) {
159
159
  const { project, held, targetRoot } = input;
160
- const checker = project.checker;
160
+ const queries = project.queries;
161
161
  const files = project.ownSourceFiles();
162
162
  const { classes, clauses, flows } = walk(files, held.declarations);
163
163
  if (classes.length === 0) {
@@ -171,15 +171,13 @@ export function interfaceSatisfaction(input) {
171
171
  ...flows.filter((flow) => flow.self === undefined).map((flow) => flow.value),
172
172
  ];
173
173
  const typeAt = new Map();
174
- for (let from = 0; from < asked.length; from += DEFAULT_BATCH_CAP) {
175
- const run = asked.slice(from, from + DEFAULT_BATCH_CAP);
176
- checker.getTypeAtLocation(run).forEach((type, index) => {
177
- const node = run[index];
178
- if (type !== undefined && node !== undefined) {
179
- typeAt.set(node, type);
180
- }
181
- });
182
- }
174
+ queries.typesAt(asked).forEach((answer, index) => {
175
+ const type = must(answer);
176
+ const node = asked[index];
177
+ if (type !== undefined && node !== undefined) {
178
+ typeAt.set(node, type);
179
+ }
180
+ });
183
181
  const classOfType = new Map();
184
182
  const instanceOf = new Map();
185
183
  for (const owner of classes) {
@@ -197,7 +195,7 @@ export function interfaceSatisfaction(input) {
197
195
  }
198
196
  let parts = constituents.get(type.id);
199
197
  if (parts === undefined) {
200
- parts = type.getTypes();
198
+ parts = must(queries.constituents(type));
201
199
  constituents.set(type.id, parts);
202
200
  }
203
201
  return parts;
@@ -213,7 +211,7 @@ export function interfaceSatisfaction(input) {
213
211
  return [];
214
212
  }
215
213
  if (!targets.has(part.id)) {
216
- targets.set(part.id, classOfType.get(part.getTarget().id));
214
+ targets.set(part.id, classOfType.get(must(queries.targetOf(part)).id));
217
215
  }
218
216
  const generic = targets.get(part.id);
219
217
  return generic === undefined ? [] : [{ owner: generic, type: part }];
@@ -222,7 +220,7 @@ export function interfaceSatisfaction(input) {
222
220
  /** The interface types one position's type is or holds, each with the interface's name. */
223
221
  const interfacesOf = (type) => partsOf(type).flatMap((part) => {
224
222
  if (!interfaceNames.has(part.id)) {
225
- const symbol = part.isObjectType() ? part.getSymbol() : undefined;
223
+ const symbol = part.isObjectType() ? must(queries.symbolOfType(part)) : undefined;
226
224
  interfaceNames.set(part.id, symbol !== undefined && (symbol.flags & SymbolFlags.Interface) !== 0
227
225
  ? symbol.name
228
226
  : undefined);
@@ -251,7 +249,8 @@ export function interfaceSatisfaction(input) {
251
249
  reach(clause.owner, source, reached.type, reached.name, clause.entry);
252
250
  }
253
251
  }
254
- for (const flow of flows) {
252
+ // The positions of every flow carrying a class are asked for at once.
253
+ const carrying = flows.flatMap((flow) => {
255
254
  const self = flow.self === undefined ? undefined : instanceOf.get(flow.self.id);
256
255
  const valueType = typeAt.get(flow.value);
257
256
  const sources = flow.self !== undefined && self !== undefined
@@ -259,38 +258,42 @@ export function interfaceSatisfaction(input) {
259
258
  : valueType === undefined
260
259
  ? []
261
260
  : classesOf(valueType);
262
- if (sources.length === 0) {
263
- continue;
264
- }
265
- const position = checker.getContextualType(flow.value);
261
+ return sources.length === 0 ? [] : [{ flow, sources }];
262
+ });
263
+ const positions = queries.contextualTypes(carrying.map((one) => one.flow.value));
264
+ carrying.forEach(({ flow, sources }, index) => {
265
+ const position = must(positions[index]);
266
266
  if (position === undefined) {
267
- continue;
267
+ return;
268
268
  }
269
269
  for (const reached of interfacesOf(position)) {
270
270
  for (const source of sources) {
271
271
  reach(source.owner, source.type, reached.type, reached.name, flow.value);
272
272
  }
273
273
  }
274
- }
274
+ });
275
275
  const chains = aliasChains(project, held);
276
276
  const required = new Map();
277
277
  const members = new Map();
278
278
  const found = [];
279
- for (const pair of pairs.values()) {
280
- if (!checker.isTypeAssignableTo(pair.source, pair.target)) {
279
+ const asking = [...pairs.values()];
280
+ const assignable = queries.assignableEach(asking);
281
+ for (const [index, pair] of asking.entries()) {
282
+ if (!must(assignable[index] ?? false)) {
281
283
  continue;
282
284
  }
283
285
  let names = required.get(pair.target.id);
284
286
  if (names === undefined) {
285
- names = new Set(checker.getPropertiesOfType(pair.target).map((property) => property.escapedName));
287
+ names = new Set(must(queries.propertiesOf(pair.target)).map((property) => property.escapedName));
286
288
  required.set(pair.target.id, names);
287
289
  }
288
290
  let answering = members.get(pair.owner.id);
289
291
  if (answering === undefined) {
290
292
  const instance = instanceOf.get(pair.owner.id) ?? pair.source;
291
- answering = new Map(checker
292
- .getPropertiesOfType(instance)
293
- .map((property) => [property.escapedName, chains.declarationsOf(property)]));
293
+ answering = new Map(must(queries.propertiesOf(instance)).map((property) => [
294
+ property.escapedName,
295
+ chains.declarationsOf(property),
296
+ ]));
294
297
  members.set(pair.owner.id, answering);
295
298
  }
296
299
  const detail = `satisfies ${pair.name}`;