@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,2 +1,91 @@
1
- /** The findings of the intra-function family, `DS1800` to `DS1899`. */
2
- export const intraFunction = () => [];
1
+ import { KINDS } from "../kinds.js";
2
+ import { UNUSED_PARAMETER, UNUSED_RESULT } from "../intra-function-parts.js";
3
+ /** A list of rules as a sentence names them. */
4
+ function spelled(rules) {
5
+ if (rules.length < 2) {
6
+ return rules.join("");
7
+ }
8
+ return `${rules.slice(0, -1).join(", ")} and ${rules.at(-1) ?? ""}`;
9
+ }
10
+ /** The clause a message ends with: the other rules that report the kind, or that none is known. */
11
+ function overlapClause(overlap) {
12
+ return overlap.length === 1 && overlap[0] === "none known"
13
+ ? "no other rule is known to report it"
14
+ : `also reported by ${spelled(overlap)}`;
15
+ }
16
+ /**
17
+ * Whether one part is reported. A parameter needs its function's signature free to
18
+ * change; a result needs that, every call in the loaded program to discard it, and every
19
+ * call site to be in the loaded program, so a function with a root or one a module
20
+ * exports to callers the run cannot see has no result to report. Every other part is
21
+ * reported wherever no project found it in use.
22
+ */
23
+ function reported(input, part, callersUnknown) {
24
+ if (part.used) {
25
+ return false;
26
+ }
27
+ const { free, discardedEverywhere } = input.intraFunction;
28
+ switch (part.code) {
29
+ case UNUSED_PARAMETER:
30
+ return free.has(part.declaration);
31
+ case UNUSED_RESULT:
32
+ return (free.has(part.declaration) &&
33
+ discardedEverywhere.has(part.declaration) &&
34
+ !callersUnknown.has(part.declaration));
35
+ default:
36
+ return true;
37
+ }
38
+ }
39
+ /**
40
+ * The findings of the intra-function family, `DS1800` to `DS1899`, each about a part of
41
+ * the declaration its reference names, at the part's own position, whatever the sweep
42
+ * decided about that declaration. Each message names the other rules that report the
43
+ * kind, as the vocabulary lists them, and so does the finding's details.
44
+ */
45
+ export const intraFunction = (input) => {
46
+ const union = input.swept.matrix.union;
47
+ const byId = new Map(union.symbols.map((symbol) => [symbol.id, symbol]));
48
+ const unknown = new Set(union.rooted.flatMap((root) => {
49
+ const symbol = union.symbols[root.at];
50
+ return symbol === undefined ? [] : [symbol.id];
51
+ }));
52
+ const { declared, loaded } = input.boundary.consumers;
53
+ const closed = input.config.consumersComplete && declared.every((id) => loaded.includes(id));
54
+ const published = (id) => {
55
+ const symbol = byId.get(id);
56
+ return (symbol !== undefined &&
57
+ symbol.kind !== "file" &&
58
+ (symbol.exported || published(symbol.parent)));
59
+ };
60
+ if (!closed) {
61
+ for (const part of input.intraFunction.parts) {
62
+ if (published(part.declaration)) {
63
+ unknown.add(part.declaration);
64
+ }
65
+ }
66
+ }
67
+ const found = [];
68
+ for (const part of input.intraFunction.parts) {
69
+ const declaration = byId.get(part.declaration);
70
+ const overlap = KINDS.get(part.code)?.overlap ?? [];
71
+ if (declaration === undefined || !reported(input, part, unknown)) {
72
+ continue;
73
+ }
74
+ found.push({
75
+ code: part.code,
76
+ position: part.position,
77
+ symbol: {
78
+ ref: declaration.ref,
79
+ kind: part.kind,
80
+ name: part.name,
81
+ sizeLines: part.position.endLine - part.position.line + 1,
82
+ },
83
+ message: `${part.message}; ${overlapClause(overlap)}`,
84
+ details: {
85
+ ...(part.kind === "store" ? { writePositions: [part.position] } : {}),
86
+ overlap,
87
+ },
88
+ });
89
+ }
90
+ return found;
91
+ };
@@ -2,6 +2,10 @@ import { REPOSITORY_DOCUMENT } from "../config.js";
2
2
  import { STALE_SUPPRESSION, SUPPRESSION_WITHOUT_REASON, } from "../suppress.js";
3
3
  /** The code a configured root that matches nothing is reported under. */
4
4
  const UNMATCHED_ROOT = "DS1704";
5
+ /** The code a configured declaration that names nothing is reported under. */
6
+ const UNMATCHED_DECLARATION = "DS1706";
7
+ /** The subject kind of a finding about a configured declaration. */
8
+ const DECLARATION_SUBJECT = "configured-declaration";
5
9
  /** The subject kind of a finding about a suppression record. */
6
10
  const SUPPRESSION_SUBJECT = "suppression";
7
11
  /** The subject kind of a finding about a configured root. */
@@ -13,6 +17,7 @@ export const NO_SELF_CHECK = {
13
17
  refusals: [],
14
18
  unmatchedRoots: [],
15
19
  rootsDocument: "",
20
+ unmatchedDeclarations: [],
16
21
  };
17
22
  /** The words a message names a mechanism by. */
18
23
  const WRITTEN = {
@@ -104,6 +109,27 @@ export function staleSuppressions(stale, files) {
104
109
  };
105
110
  });
106
111
  }
112
+ /** The first position of the document that configured a setting, or of the conventional one. */
113
+ function documentPosition(document) {
114
+ return {
115
+ path: document === "" ? REPOSITORY_DOCUMENT : document,
116
+ line: DOCUMENT_POSITION,
117
+ column: DOCUMENT_POSITION,
118
+ endLine: DOCUMENT_POSITION,
119
+ };
120
+ }
121
+ /**
122
+ * One finding per configured declaration that named no declaration in any project, at the
123
+ * first position of the document that configured its key, as a configured root's is.
124
+ */
125
+ function unmatchedDeclarationFindings(unmatched) {
126
+ return unmatched.map((one) => ({
127
+ code: UNMATCHED_DECLARATION,
128
+ position: documentPosition(one.document),
129
+ symbol: { ref: one.ref, kind: DECLARATION_SUBJECT, name: one.ref, sizeLines: 1 },
130
+ message: `${one.key} entry names no declaration in any project`,
131
+ }));
132
+ }
107
133
  /**
108
134
  * One finding per configured root or pattern that named nothing in any project. A finding
109
135
  * names the document that declared the roots, or the conventional repository configuration
@@ -112,12 +138,7 @@ export function staleSuppressions(stale, files) {
112
138
  * own, so the string each finding names tells one from another.
113
139
  */
114
140
  export function unmatchedRootFindings(sources, document) {
115
- const position = {
116
- path: document === "" ? REPOSITORY_DOCUMENT : document,
117
- line: DOCUMENT_POSITION,
118
- column: DOCUMENT_POSITION,
119
- endLine: DOCUMENT_POSITION,
120
- };
141
+ const position = documentPosition(document);
121
142
  return sources.map((source) => ({
122
143
  code: UNMATCHED_ROOT,
123
144
  position,
@@ -129,11 +150,13 @@ export function unmatchedRootFindings(sources, document) {
129
150
  }
130
151
  /**
131
152
  * The findings of the self-check family, `DS1700` to `DS1799`: a suppression refused for
132
- * carrying no reason or naming no path, and a configured root that named nothing. A
153
+ * carrying no reason or naming no path, and a configured root or declaration that named
154
+ * nothing. A
133
155
  * suppression that matched nothing is a record of the report's stale suppressions, which
134
156
  * {@link staleSuppressions} builds, and a stale cross-language edge is the merge's to report.
135
157
  */
136
158
  export const selfCheck = ({ selfCheck: facts, swept }) => [
137
159
  ...refusedSuppressions(facts.refusals, fileRefsOf(swept.matrix.union.symbols)),
138
160
  ...unmatchedRootFindings(facts.unmatchedRoots, facts.rootsDocument),
161
+ ...unmatchedDeclarationFindings(facts.unmatchedDeclarations),
139
162
  ];
@@ -111,7 +111,15 @@ function messageOf(code, judged) {
111
111
  return `${subject} has no reference in the target`;
112
112
  }
113
113
  /** The findings of the unused-declarations family, `DS1000` to `DS1099`. */
114
- export const unusedDeclarations = (input) => {
114
+ export const unusedDeclarations = (input) => familyFindings(input, false);
115
+ /**
116
+ * The finding each member of a dead container would carry were its container live: what
117
+ * a record on such a member is matched against, since the report carries the container's
118
+ * finding rather than one of the member's own.
119
+ */
120
+ export const fallenMembers = (input) => familyFindings(input, true);
121
+ /** The family's findings, or, where `fallen` is set, its dead containers' members' alone. */
122
+ function familyFindings(input, fallen) {
115
123
  const union = input.swept.matrix.union;
116
124
  const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
117
125
  const unreachable = unreachableExports(input);
@@ -122,11 +130,15 @@ export const unusedDeclarations = (input) => {
122
130
  if (symbol === undefined) {
123
131
  continue;
124
132
  }
133
+ const containerDead = dead.has(symbol.parent);
134
+ if (fallen !== containerDead) {
135
+ continue;
136
+ }
125
137
  const judged = {
126
138
  candidate,
127
139
  symbol,
128
140
  test: union.test[at] === true,
129
- containerDead: dead.has(symbol.parent),
141
+ containerDead: containerDead && !fallen,
130
142
  deprecated: input.deprecated.has(symbol.id),
131
143
  unreachable: unreachable.has(symbol.id),
132
144
  };
@@ -148,4 +160,4 @@ export const unusedDeclarations = (input) => {
148
160
  });
149
161
  }
150
162
  return found;
151
- };
163
+ }
@@ -76,18 +76,25 @@ export function verdictOf(result, config) {
76
76
  return failing ? EXIT_FINDINGS : EXIT_CLEAN;
77
77
  }
78
78
  /**
79
- * The findings of a report as a baseline records them, in report order. A finding about a
80
- * row of a document is not recorded: no record withholds one, so a row recording it would
81
- * be stale on the first read back.
79
+ * The rows one round of a baseline write records from its findings, in their order. A
80
+ * finding about a row of a document is not recorded: no record withholds one, so a row
81
+ * recording it would be stale on the first read back. Two findings that would record the
82
+ * same row, two parts of one declaration under one code, record it once.
82
83
  */
83
- export function recordedFindings(result) {
84
- return result.findings
85
- .filter((finding) => !isRowSubject(finding.symbol.kind))
86
- .map((finding) => ({
87
- code: finding.code,
88
- symbol: finding.symbol.ref,
89
- path: finding.position.path,
90
- }));
84
+ export function recordedFindings(findings) {
85
+ const rows = new Map();
86
+ for (const finding of findings) {
87
+ if (isRowSubject(finding.symbol.kind)) {
88
+ continue;
89
+ }
90
+ const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
91
+ rows.set(rowKey(row), rows.get(rowKey(row)) ?? row);
92
+ }
93
+ return [...rows.values()];
94
+ }
95
+ /** One row's identity: its code, its symbol and its path. */
96
+ export function rowKey(row) {
97
+ return `${row.code}\u0000${row.symbol}\u0000${row.path}`;
91
98
  }
92
99
  /** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
93
100
  export function analyzerProvenance(version) {
@@ -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}`;