@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.
- package/README.md +4 -4
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +217 -48
- package/dist/src/calls.js +23 -22
- package/dist/src/class-members.js +10 -11
- package/dist/src/components.js +4 -2
- package/dist/src/configured-declarations.js +56 -7
- package/dist/src/conformance-record.js +2 -2
- 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/edges.js +4 -1
- 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/completion.js +37 -8
- package/dist/src/findings/emitters.js +20 -6
- package/dist/src/findings/interfaces.js +7 -1
- package/dist/src/findings/intra-function.js +91 -2
- package/dist/src/findings/self-check.js +30 -7
- package/dist/src/findings/unused-declarations.js +15 -3
- package/dist/src/findings-pass.js +18 -11
- 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 +660 -0
- package/dist/src/inventory.js +85 -4
- package/dist/src/json-chunks.js +76 -0
- package/dist/src/kinds.js +337 -35
- 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 +46 -4
- package/dist/src/report.js +14 -3
- package/dist/src/reporters/annotations.js +9 -5
- package/dist/src/reporters/json.js +3 -2
- package/dist/src/reporters/sarif.js +22 -16
- package/dist/src/reporters/template.js +112 -12
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/serialization-contract.js +53 -32
- package/dist/src/session.js +79 -39
- package/dist/src/signatures.js +32 -0
- package/dist/src/suppress-file.js +29 -7
- package/dist/src/suppress-inline.js +8 -1
- package/dist/src/suppress.js +27 -0
- package/dist/src/sweep.js +4 -1
- package/dist/src/value-reach.js +44 -25
- package/dist/src/verbs/analyze.js +39 -14
- package/dist/src/verbs/describe.js +12 -8
- 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/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +75 -11
- package/src/analysis.ts +385 -104
- package/src/calls.ts +10 -9
- package/src/class-members.ts +10 -11
- package/src/components.ts +4 -2
- package/src/configured-declarations.ts +80 -8
- package/src/conformance-record.ts +2 -2
- 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/edges.ts +4 -1
- package/src/entry-points.ts +7 -0
- package/src/enum-group.ts +56 -42
- package/src/exempt.ts +50 -2
- package/src/finding.ts +5 -1
- package/src/findings/completion.ts +46 -10
- package/src/findings/emitter.ts +3 -0
- package/src/findings/emitters.ts +27 -6
- package/src/findings/interfaces.ts +7 -1
- package/src/findings/intra-function.ts +106 -3
- package/src/findings/self-check.ts +48 -7
- package/src/findings/unused-declarations.ts +18 -4
- package/src/findings-pass.ts +26 -11
- 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 +889 -0
- package/src/inventory.ts +97 -4
- package/src/json-chunks.ts +87 -0
- package/src/kinds.ts +355 -41
- package/src/partial.ts +40 -0
- package/src/query.ts +522 -0
- package/src/references.ts +119 -38
- package/src/reflective-lookup.ts +56 -6
- package/src/report.ts +32 -4
- 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 +27 -18
- package/src/reporters/template.ts +119 -14
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/serialization-contract.ts +59 -40
- package/src/session.ts +146 -58
- package/src/signatures.ts +55 -0
- package/src/suppress-file.ts +40 -8
- package/src/suppress-inline.ts +8 -1
- package/src/suppress.ts +31 -0
- package/src/sweep.ts +9 -4
- package/src/value-reach.ts +66 -28
- package/src/verbs/analyze.ts +51 -14
- package/src/verbs/describe.ts +13 -9
- 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
- package/src/version.ts +1 -1
- package/dist/src/reporters/rules.js +0 -178
- 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:
|
|
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
|
+
}
|
package/src/findings-pass.ts
CHANGED
|
@@ -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
|
|
127
|
-
* row of a document is not recorded: no record withholds one, so a row
|
|
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(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
-
?
|
|
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 =
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
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
|
|
39
|
-
*
|
|
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
|
-
|
|
43
|
+
writeDocument(path: string, pieces: Iterable<string>): void;
|
|
42
44
|
}
|
package/src/implementations.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 =
|
|
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
|
-
/**
|
|
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
|
|
174
|
+
return;
|
|
148
175
|
}
|
|
149
176
|
|
|
150
|
-
const
|
|
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 ??=
|
|
208
|
+
(at.members ??= must(queries.propertiesOf(at.type)));
|
|
182
209
|
const classMembers = (at: ClassAt): ReadonlyMap<string, TSSymbol> =>
|
|
183
210
|
(at.members ??= new Map(
|
|
184
|
-
|
|
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(
|
|
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
|
-
|
|
286
|
+
candidates.push({ at, implementer });
|
|
259
287
|
}
|
|
260
288
|
}
|
|
261
289
|
}
|
|
262
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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 =
|
|
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(
|
|
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() ?
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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
|
-
|
|
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
|
-
|
|
398
|
-
.
|
|
399
|
-
|
|
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
|
}
|