@cplieger/deadset-ts 8.0.0-dev.17 → 8.0.0-dev.19
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/dist/src/analysis.js +30 -72
- package/dist/src/augmentations.js +1 -1
- package/dist/src/calls.js +267 -48
- package/dist/src/commands/held.js +499 -0
- package/dist/src/commands/read.js +16 -10
- package/dist/src/commands/shell.js +109 -15
- package/dist/src/commands/tools.js +1540 -149
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +9 -2
- package/dist/src/entries/conventions.js +7 -6
- package/dist/src/entries/test-runners.js +6 -40
- package/dist/src/entries/workflow-steps.js +202 -28
- package/dist/src/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/findings/completion.js +4 -17
- package/dist/src/findings/emitters.js +22 -33
- package/dist/src/findings/intra-function.js +3 -3
- package/dist/src/findings/non-code-artifacts.js +11 -5
- package/dist/src/findings/reads-and-writes.js +71 -15
- package/dist/src/findings/unused-declarations.js +2 -22
- package/dist/src/findings/visibility-narrowing.js +14 -20
- package/dist/src/inventory.js +18 -0
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +38 -21
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/query.js +46 -0
- package/dist/src/references/sink.js +87 -1
- package/dist/src/references.js +1 -1
- package/dist/src/roots/manifest.js +47 -4
- package/dist/src/roots/published-reach.js +4 -4
- package/dist/src/roots/root-set.js +14 -16
- package/dist/src/text/named-files.js +257 -34
- package/dist/src/verbs/analyze.js +16 -1
- package/dist/src/verbs/explain.js +73 -152
- package/dist/src/verbs/print-retained.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +54 -108
- package/src/augmentations.ts +1 -1
- package/src/calls.ts +326 -52
- package/src/commands/held.ts +632 -0
- package/src/commands/read.ts +26 -12
- package/src/commands/shell.ts +117 -15
- package/src/commands/tools.ts +1972 -202
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +9 -2
- package/src/dependencies.ts +1 -1
- package/src/entries/conventions.ts +8 -12
- package/src/entries/test-runners.ts +12 -45
- package/src/entries/workflow-steps.ts +227 -38
- package/src/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/file-facts.ts +5 -0
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +7 -0
- package/src/findings/completion.ts +5 -19
- package/src/findings/emitters.ts +23 -41
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/non-code-artifacts.ts +16 -5
- package/src/findings/reads-and-writes.ts +99 -26
- package/src/findings/unused-declarations.ts +4 -28
- package/src/findings/visibility-narrowing.ts +25 -26
- package/src/interface-satisfaction.ts +1 -1
- package/src/inventory.ts +19 -0
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +74 -26
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/query.ts +81 -0
- package/src/references/sink.ts +101 -0
- package/src/references.ts +8 -1
- package/src/roots/manifest.ts +53 -8
- package/src/roots/published-reach.ts +9 -4
- package/src/roots/root-set.ts +18 -16
- package/src/stores.ts +2 -1
- package/src/tests/file-class.ts +19 -2
- package/src/text/named-files.ts +333 -36
- package/src/verbs/analyze.ts +16 -1
- package/src/verbs/explain.ts +81 -199
- package/src/verbs/print-retained.ts +1 -1
- package/dist/src/matrix.js +0 -275
- package/dist/src/sweep.js +0 -234
- package/src/matrix.ts +0 -466
- package/src/sweep.ts +0 -384
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.js";
|
|
2
|
+
import { WORDS } from "../inventory.js";
|
|
2
3
|
import { positionKey } from "../position.js";
|
|
3
4
|
import { declarationKey } from "../ref.js";
|
|
5
|
+
import { reachabilityClasses } from "./completion.js";
|
|
4
6
|
import { bytewise } from "../text/compare.js";
|
|
5
7
|
/** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
|
|
6
8
|
const SIGNATURES = new Set(["function", "method", "type"]);
|
|
@@ -70,14 +72,16 @@ function parametersOnlyNamedBy(run, namedBy, member) {
|
|
|
70
72
|
.map((symbol) => symbol.name);
|
|
71
73
|
}
|
|
72
74
|
/** What one write-only finding says, the declarations its deletion takes with it included. */
|
|
73
|
-
function writeOnlyMessage(run,
|
|
75
|
+
function writeOnlyMessage(run, written, subject) {
|
|
76
|
+
const { symbol, held } = subject;
|
|
74
77
|
const writes = held.writes.length === 1 ? "once" : `at ${String(held.writes.length)} positions`;
|
|
75
78
|
const read = run.input.stores.readInTests.has(symbol.id)
|
|
76
79
|
? "read only from test files"
|
|
77
80
|
: "never read";
|
|
78
81
|
const cascade = [
|
|
79
|
-
...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
82
|
+
...parametersOnlyNamedBy(run, written.namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
80
83
|
...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
|
|
84
|
+
...(written.named.get(symbol.id) ?? []),
|
|
81
85
|
];
|
|
82
86
|
const word = SUBJECT_WORDS[symbol.kind] ?? "member";
|
|
83
87
|
const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
|
|
@@ -86,16 +90,34 @@ function writeOnlyMessage(run, namedBy, symbol, held) {
|
|
|
86
90
|
: `${message}, and deleting it with its writes deletes ${cascade.join(" and ")} too`;
|
|
87
91
|
}
|
|
88
92
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
93
|
+
* The kinds of declaration that fall with a write-only subject: a declaration of its own.
|
|
94
|
+
* A member falls with its container, and a type parameter is the type-parameter clause's.
|
|
95
|
+
*/
|
|
96
|
+
const FALLING = new Set([
|
|
97
|
+
"function",
|
|
98
|
+
"class",
|
|
99
|
+
"interface",
|
|
100
|
+
"type",
|
|
101
|
+
"enum",
|
|
102
|
+
"namespace",
|
|
103
|
+
"variable",
|
|
104
|
+
]);
|
|
105
|
+
const WRITTEN = new WeakMap();
|
|
106
|
+
/**
|
|
107
|
+
* `DS1301`'s subjects: a module-level variable, a type member or a class member the
|
|
108
|
+
* counted references store into and never read.
|
|
91
109
|
*
|
|
92
110
|
* The subject is a live declaration: a store is a reference, so the sweep holds a
|
|
93
111
|
* written one live and its candidates are never this kind's. Every reference that is
|
|
94
112
|
* not a store reads, and so does an exemption record naming the declaration.
|
|
95
113
|
*/
|
|
96
|
-
function
|
|
114
|
+
function writtenOf(run) {
|
|
115
|
+
const known = WRITTEN.get(run.input);
|
|
116
|
+
if (known !== undefined) {
|
|
117
|
+
return known;
|
|
118
|
+
}
|
|
97
119
|
const { stores } = run.input;
|
|
98
|
-
const { matrix } = run.input.swept;
|
|
120
|
+
const { matrix, sweep } = run.input.swept;
|
|
99
121
|
const uses = new Map();
|
|
100
122
|
for (const reference of stores.references) {
|
|
101
123
|
let held = uses.get(reference.to);
|
|
@@ -124,22 +146,49 @@ function writeOnlySymbols(run) {
|
|
|
124
146
|
namedBy.set(reference.to, (namedBy.get(reference.to) ?? new Set()).add(reference.from));
|
|
125
147
|
}
|
|
126
148
|
}
|
|
127
|
-
const
|
|
128
|
-
for (const symbol of matrix.union.symbols) {
|
|
149
|
+
const subjects = matrix.union.symbols.flatMap((symbol) => {
|
|
129
150
|
const held = uses.get(symbol.id);
|
|
130
|
-
|
|
151
|
+
return held === undefined ||
|
|
131
152
|
held.writes.length === 0 ||
|
|
132
153
|
held.reads > 0 ||
|
|
133
154
|
!writable(run, symbol) ||
|
|
134
155
|
stores.exempt.has(symbol.id) ||
|
|
135
|
-
run.candidates.has(symbol.id)
|
|
156
|
+
run.candidates.has(symbol.id)
|
|
157
|
+
? []
|
|
158
|
+
: [{ symbol, held }];
|
|
159
|
+
});
|
|
160
|
+
const subjectIds = new Set(subjects.map((subject) => subject.symbol.id));
|
|
161
|
+
const classOf = reachabilityClasses(run.input);
|
|
162
|
+
const named = new Map();
|
|
163
|
+
const falls = new Set();
|
|
164
|
+
for (const symbol of matrix.union.symbols) {
|
|
165
|
+
const chains = sweep.carried.get(symbol.id);
|
|
166
|
+
if (chains === undefined ||
|
|
167
|
+
subjectIds.has(symbol.id) ||
|
|
168
|
+
!FALLING.has(symbol.kind) ||
|
|
169
|
+
classOf(symbol.id) !== "certain") {
|
|
136
170
|
continue;
|
|
137
171
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
172
|
+
const innermost = chains.map((chain) => chain.find((id) => subjectIds.has(id)));
|
|
173
|
+
if (innermost.includes(undefined)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
falls.add(symbol.id);
|
|
177
|
+
for (const subject of new Set(innermost)) {
|
|
178
|
+
const name = `${WORDS[symbol.kind]} ${symbol.name}`;
|
|
179
|
+
named.set(subject ?? "", [...(named.get(subject ?? "") ?? []), name]);
|
|
180
|
+
}
|
|
141
181
|
}
|
|
142
|
-
|
|
182
|
+
const written = { subjects, namedBy, named, falls };
|
|
183
|
+
WRITTEN.set(run.input, written);
|
|
184
|
+
return written;
|
|
185
|
+
}
|
|
186
|
+
/** `DS1301`: each subject, named with every position written and what falls with it. */
|
|
187
|
+
function writeOnlySymbols(run) {
|
|
188
|
+
const written = writtenOf(run);
|
|
189
|
+
return written.subjects.map((subject) => findingAbout(subject.symbol, CODE.writeOnlySymbol, writeOnlyMessage(run, written, subject), {
|
|
190
|
+
writePositions: subject.held.writes,
|
|
191
|
+
}));
|
|
143
192
|
}
|
|
144
193
|
/**
|
|
145
194
|
* The candidates no reference of any file names whose container is not itself dead, of
|
|
@@ -184,7 +233,14 @@ function unusedTypeParameters(run) {
|
|
|
184
233
|
* supersedes narrowing it.
|
|
185
234
|
*/
|
|
186
235
|
export function writeOnlyDeclarations(input) {
|
|
187
|
-
return new Set(
|
|
236
|
+
return new Set(writtenOf(indexed(input)).subjects.map(({ symbol }) => declarationKey(symbol.ref, symbol.position.path)));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The declarations that fall with a write-only subject, whatever the severity: a finding
|
|
240
|
+
* about one would name code its subject's deletion already deletes.
|
|
241
|
+
*/
|
|
242
|
+
export function fallingWithWrites(input) {
|
|
243
|
+
return writtenOf(indexed(input)).falls;
|
|
188
244
|
}
|
|
189
245
|
/**
|
|
190
246
|
* The findings of the reads-and-writes kinds, `DS1301`, `DS1005` and `DS1006`, in that
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.js";
|
|
2
|
-
import {
|
|
2
|
+
import { WORDS } from "../inventory.js";
|
|
3
3
|
import { unreachableExports } from "./visibility-narrowing.js";
|
|
4
4
|
/** The kinds of declaration that are members of a class, an interface, a type or an enum. */
|
|
5
5
|
const MEMBERS = new Set([
|
|
@@ -19,24 +19,6 @@ const NOT_REPORTED_HERE = new Set([
|
|
|
19
19
|
"interface-method",
|
|
20
20
|
"type-parameter",
|
|
21
21
|
]);
|
|
22
|
-
/** The reader's word for each kind of declaration a finding of this emitter names. */
|
|
23
|
-
const WORDS = {
|
|
24
|
-
file: "file",
|
|
25
|
-
function: "function",
|
|
26
|
-
class: "class",
|
|
27
|
-
interface: "interface",
|
|
28
|
-
type: "type alias",
|
|
29
|
-
enum: "enum",
|
|
30
|
-
namespace: "namespace",
|
|
31
|
-
variable: "variable",
|
|
32
|
-
"export-alias": "re-export",
|
|
33
|
-
method: "method",
|
|
34
|
-
"class-member": "class member",
|
|
35
|
-
"interface-method": "interface method",
|
|
36
|
-
"type-member": "type member",
|
|
37
|
-
"enum-member": "enum member",
|
|
38
|
-
"type-parameter": "type parameter",
|
|
39
|
-
};
|
|
40
22
|
/** What the test-of-dead-code kind says, which states the rule it applies. */
|
|
41
23
|
const TEST_OF_DEAD_CODE = "every target symbol this test references is reported dead";
|
|
42
24
|
/**
|
|
@@ -50,7 +32,7 @@ const TEST_OF_DEAD_CODE = "every target symbol this test references is reported
|
|
|
50
32
|
function codeOf(judged) {
|
|
51
33
|
const { candidate, symbol } = judged;
|
|
52
34
|
if (candidate.testOfDeadCode) {
|
|
53
|
-
return
|
|
35
|
+
return CODE.testOfDeadCode;
|
|
54
36
|
}
|
|
55
37
|
if ((judged.test && !candidate.unreferencedTest) || NOT_REPORTED_HERE.has(symbol.kind)) {
|
|
56
38
|
return undefined;
|
|
@@ -201,7 +183,6 @@ function emitterFindings(input, fallen) {
|
|
|
201
183
|
const union = input.swept.matrix.union;
|
|
202
184
|
const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
|
|
203
185
|
const unreachable = unreachableExports(input);
|
|
204
|
-
const unseenAPI = testsUnseenAPI(input);
|
|
205
186
|
let brands;
|
|
206
187
|
const found = [];
|
|
207
188
|
for (const candidate of input.swept.sweep.candidates) {
|
|
@@ -220,7 +201,6 @@ function emitterFindings(input, fallen) {
|
|
|
220
201
|
test: union.test[at] === true,
|
|
221
202
|
deprecated: input.deprecated.has(symbol.id),
|
|
222
203
|
unreachable: unreachable.has(symbol.id),
|
|
223
|
-
testsUnseenAPI: candidate.testOfDeadCode && unseenAPI(candidate.id),
|
|
224
204
|
};
|
|
225
205
|
const code = codeOf(judged);
|
|
226
206
|
if (code === undefined ||
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.js";
|
|
2
|
-
import { OUTSIDE } from "../graph.js";
|
|
2
|
+
import { OUTSIDE } from "../liveness/graph.js";
|
|
3
|
+
import { WORDS } from "../inventory.js";
|
|
3
4
|
import { declarationKey } from "../ref.js";
|
|
4
|
-
import { runnerCalls } from "../sweep.js";
|
|
5
|
+
import { runnerCalls } from "../liveness/sweep.js";
|
|
6
|
+
import { exposedTypes, exposes } from "../exposure.js";
|
|
5
7
|
import { writeOnlyDeclarations } from "./reads-and-writes.js";
|
|
6
8
|
/**
|
|
7
9
|
* The root kinds by which a consumer importing the package reaches a declaration. A
|
|
@@ -9,17 +11,6 @@ import { writeOnlyDeclarations } from "./reads-and-writes.js";
|
|
|
9
11
|
* from outside the reference graph: a runtime, a tool, a framework or the configuration.
|
|
10
12
|
*/
|
|
11
13
|
const PUBLISHING = new Set(["manifest-entry", "published-api"]);
|
|
12
|
-
/** The reader's word for each kind of declaration this emitter reports. */
|
|
13
|
-
const WORDS = {
|
|
14
|
-
function: "function",
|
|
15
|
-
class: "class",
|
|
16
|
-
interface: "interface",
|
|
17
|
-
type: "type alias",
|
|
18
|
-
enum: "enum",
|
|
19
|
-
namespace: "namespace",
|
|
20
|
-
variable: "variable",
|
|
21
|
-
"export-alias": "re-export",
|
|
22
|
-
};
|
|
23
14
|
function publishes(kinds) {
|
|
24
15
|
return [...(kinds ?? [])].some((kind) => PUBLISHING.has(kind));
|
|
25
16
|
}
|
|
@@ -102,9 +93,6 @@ function topLevelExport(facts, at, symbol) {
|
|
|
102
93
|
const parent = facts.graph.parent[at] ?? OUTSIDE;
|
|
103
94
|
return symbol.exported && parent !== OUTSIDE && facts.graph.symbols[parent]?.kind === "file";
|
|
104
95
|
}
|
|
105
|
-
function wordOf(symbol) {
|
|
106
|
-
return WORDS[symbol.kind] ?? symbol.kind;
|
|
107
|
-
}
|
|
108
96
|
/** The visibility each narrowing code names, which only the narrowing codes carry. */
|
|
109
97
|
const NARROWER = {
|
|
110
98
|
[CODE.redundantExportKeyword]: "file",
|
|
@@ -145,7 +133,7 @@ function narrowing(facts, at, symbol, world) {
|
|
|
145
133
|
(facts.named[at] ?? 0) === 0) {
|
|
146
134
|
return undefined;
|
|
147
135
|
}
|
|
148
|
-
const word =
|
|
136
|
+
const word = WORDS[symbol.kind];
|
|
149
137
|
switch (facts.reach[at]) {
|
|
150
138
|
case "file":
|
|
151
139
|
if (facts.published[file] === true && !closed) {
|
|
@@ -153,7 +141,7 @@ function narrowing(facts, at, symbol, world) {
|
|
|
153
141
|
}
|
|
154
142
|
return findingOf(symbol, CODE.redundantExportKeyword, `exported ${word} is referenced only inside the file that declares it`);
|
|
155
143
|
case "package":
|
|
156
|
-
if (!closed || !publishes(facts.rootedBy[at])) {
|
|
144
|
+
if (!closed || !publishes(facts.rootedBy[at]) || exposes(world.exposed, symbol)) {
|
|
157
145
|
return undefined;
|
|
158
146
|
}
|
|
159
147
|
return findingOf(symbol, CODE.unnecessaryExport, `exported ${word} is referenced only inside the package that declares it`);
|
|
@@ -181,7 +169,7 @@ function unreachable(facts, at, symbol, candidate, input) {
|
|
|
181
169
|
const found = candidate.relation === "reachability"
|
|
182
170
|
? "is referenced only from declarations that are themselves dead"
|
|
183
171
|
: "has no reference in the target";
|
|
184
|
-
return findingOf(symbol, CODE.unreachableExport, `exported ${
|
|
172
|
+
return findingOf(symbol, CODE.unreachableExport, `exported ${WORDS[symbol.kind]} ${found}, and nothing outside can import the file that declares it`);
|
|
185
173
|
}
|
|
186
174
|
/**
|
|
187
175
|
* The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
|
|
@@ -194,7 +182,13 @@ export function narrowings(input) {
|
|
|
194
182
|
const facts = factsOf(input.swept.matrix.union);
|
|
195
183
|
const { declared, loaded } = input.boundary.consumers;
|
|
196
184
|
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
197
|
-
const world = {
|
|
185
|
+
const world = {
|
|
186
|
+
closed,
|
|
187
|
+
writeOnly: writeOnlyDeclarations(input),
|
|
188
|
+
exposed: closed
|
|
189
|
+
? exposedTypes(facts.graph, facts.reach.map((reach) => reach === "outside"), input.boundary.carriers)
|
|
190
|
+
: new Set(),
|
|
191
|
+
};
|
|
198
192
|
const candidates = new Map(input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
199
193
|
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
200
194
|
const findings = [];
|
package/dist/src/inventory.js
CHANGED
|
@@ -2,6 +2,24 @@ import { isClassDeclaration, isComputedPropertyName, isConstructorDeclaration, i
|
|
|
2
2
|
import { byPosition, positionKey, renderPosition, startLine } from "./position.js";
|
|
3
3
|
import { renderRef, spell } from "./ref.js";
|
|
4
4
|
import { signatureOf } from "./signatures.js";
|
|
5
|
+
/** The reader's word for each kind of declaration, as a finding names it. */
|
|
6
|
+
export const WORDS = {
|
|
7
|
+
file: "file",
|
|
8
|
+
function: "function",
|
|
9
|
+
class: "class",
|
|
10
|
+
interface: "interface",
|
|
11
|
+
type: "type alias",
|
|
12
|
+
enum: "enum",
|
|
13
|
+
namespace: "namespace",
|
|
14
|
+
variable: "variable",
|
|
15
|
+
"export-alias": "re-export",
|
|
16
|
+
method: "method",
|
|
17
|
+
"class-member": "class member",
|
|
18
|
+
"interface-method": "interface method",
|
|
19
|
+
"type-member": "type member",
|
|
20
|
+
"enum-member": "enum member",
|
|
21
|
+
"type-parameter": "type parameter",
|
|
22
|
+
};
|
|
5
23
|
/**
|
|
6
24
|
* One node's identity inside one program, which is how two readings of that program name
|
|
7
25
|
* one declaration: the binder's table and the tree meet here, and so does the reference
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and every dead declaration that falls with them, being dead only through them.
|
|
5
5
|
*/
|
|
6
6
|
import { OUTSIDE } from "./graph.js";
|
|
7
|
-
import { PRODUCT } from "
|
|
7
|
+
import { PRODUCT } from "../product.js";
|
|
8
8
|
/** The fewest digits a component identifier's number is written with. */
|
|
9
9
|
const ID_DIGITS = 4;
|
|
10
10
|
/** The identifier of the component at one place of the run's order, counted from one. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* reachability, indexed for the sweep. The graph reads no file and asks the
|
|
4
|
-
* nothing, so a sweep over it is a function of the values it was built from, and
|
|
5
|
-
* inventory's order is the order every set a sweep answers reads in.
|
|
2
|
+
* The run's declarations, the references every configuration makes between them and the
|
|
3
|
+
* roots that seed reachability, indexed for the sweep. The graph reads no file and asks the
|
|
4
|
+
* compiler nothing, so a sweep over it is a function of the values it was built from, and
|
|
5
|
+
* the inventory's order is the order every set a sweep answers reads in.
|
|
6
6
|
*/
|
|
7
|
-
import { madeByTestCode } from "
|
|
7
|
+
import { madeByTestCode } from "../tests/file-class.js";
|
|
8
8
|
/** The position of a declaration the inventory does not hold. */
|
|
9
9
|
export const OUTSIDE = -1;
|
|
10
10
|
/**
|
|
@@ -67,49 +67,63 @@ export function swept(kind) {
|
|
|
67
67
|
export function namesACaller(kind) {
|
|
68
68
|
return NAMES_A_CALLER[kind];
|
|
69
69
|
}
|
|
70
|
+
/** Adds one reference's count to the counts of the declaration it names. */
|
|
71
|
+
function count(counts, test, consumer) {
|
|
72
|
+
if (test) {
|
|
73
|
+
counts.test += 1;
|
|
74
|
+
counts.consumerTest += consumer ? 1 : 0;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
counts.production += 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
70
80
|
/**
|
|
71
|
-
* Indexes
|
|
81
|
+
* Indexes the run's inventory, references and roots. `testFiles` are the paths the
|
|
72
82
|
* reference pass classified as test files, so a declaration one holds is a test
|
|
73
83
|
* declaration. A reference to a declaration the inventory does not hold is dropped; one
|
|
74
84
|
* from such a declaration still counts toward its target, because reference counting
|
|
75
85
|
* asks whether any declaration names the target. A root naming nothing is dropped.
|
|
76
86
|
*/
|
|
77
|
-
export function graphOf(symbols, references, roots, testFiles, supportFiles = [], ambient = new Set()) {
|
|
87
|
+
export function graphOf(symbols, references, roots, testFiles, supportFiles = [], ambient = new Set(), sources = {}) {
|
|
78
88
|
const supports = new Set(supportFiles);
|
|
79
89
|
const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
|
|
80
90
|
const at = (id) => index.get(id) ?? OUTSIDE;
|
|
81
91
|
const tests = new Set(testFiles);
|
|
92
|
+
const unjudged = sources.unjudged ?? new Set();
|
|
82
93
|
const out = symbols.map(() => []);
|
|
83
94
|
const made = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
|
|
95
|
+
const guessedMade = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
|
|
84
96
|
const consumed = [];
|
|
85
|
-
|
|
97
|
+
references.forEach((reference, place) => {
|
|
86
98
|
const to = at(reference.to);
|
|
87
99
|
if (to === OUTSIDE) {
|
|
88
|
-
|
|
100
|
+
return;
|
|
89
101
|
}
|
|
90
102
|
const test = madeByTestCode(reference, supports);
|
|
103
|
+
const consumer = reference.consumer !== undefined;
|
|
104
|
+
const guessed = reference.guessed === true;
|
|
91
105
|
const counts = made[to];
|
|
92
106
|
if (counts !== undefined) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
counts.production += 1;
|
|
99
|
-
}
|
|
107
|
+
count(counts, test, consumer);
|
|
108
|
+
}
|
|
109
|
+
const guesses = guessedMade[to];
|
|
110
|
+
if (guessed && guesses !== undefined) {
|
|
111
|
+
count(guesses, test, consumer);
|
|
100
112
|
}
|
|
101
113
|
const edge = {
|
|
102
114
|
to,
|
|
115
|
+
reference: place,
|
|
103
116
|
test,
|
|
117
|
+
guessed,
|
|
104
118
|
evaluation: reference.use === "evaluation",
|
|
105
|
-
consumer
|
|
119
|
+
consumer,
|
|
106
120
|
};
|
|
107
|
-
if (
|
|
121
|
+
if (consumer || reference.unheld === true || unjudged.has(reference.from)) {
|
|
108
122
|
consumed.push(edge);
|
|
109
|
-
|
|
123
|
+
return;
|
|
110
124
|
}
|
|
111
125
|
out[at(reference.from)]?.push(edge);
|
|
112
|
-
}
|
|
126
|
+
});
|
|
113
127
|
const parent = symbols.map((symbol) => at(symbol.parent));
|
|
114
128
|
const exportsOf = symbols.map(() => []);
|
|
115
129
|
symbols.forEach((symbol, position) => {
|
|
@@ -122,13 +136,16 @@ export function graphOf(symbols, references, roots, testFiles, supportFiles = []
|
|
|
122
136
|
for (const root of roots) {
|
|
123
137
|
const position = at(root.id);
|
|
124
138
|
if (position !== OUTSIDE) {
|
|
125
|
-
rooted.push({ at: position, kind: root.kind });
|
|
139
|
+
rooted.push({ at: position, kind: root.kind, source: root.source });
|
|
126
140
|
}
|
|
127
141
|
}
|
|
128
142
|
return {
|
|
129
143
|
symbols,
|
|
130
144
|
out,
|
|
131
145
|
made,
|
|
146
|
+
guessedMade,
|
|
147
|
+
references,
|
|
148
|
+
configurations: sources.configurations ?? references.map(() => []),
|
|
132
149
|
parent,
|
|
133
150
|
test: symbols.map((symbol) => tests.has(symbol.position.path)),
|
|
134
151
|
support: symbols.map((symbol) => supports.has(symbol.position.path)),
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One run's projects as one matrix: every declaration any project judges, keyed by its
|
|
3
|
+
* position and carrying the configurations that hold it, and one graph over every
|
|
4
|
+
* configuration's references and roots, which the sweep judges once (matrix.4, matrix.5).
|
|
5
|
+
*/
|
|
6
|
+
import { graphOf, swept } from "./graph.js";
|
|
7
|
+
import { byPosition } from "../position.js";
|
|
8
|
+
/**
|
|
9
|
+
* What makes one reference one reference, whichever configuration made it. A consumer's
|
|
10
|
+
* position is rendered against its own root, so the consumer is part of what it names.
|
|
11
|
+
*/
|
|
12
|
+
export function referenceKey(reference) {
|
|
13
|
+
const { path, line, column } = reference.position;
|
|
14
|
+
return [
|
|
15
|
+
reference.consumer ?? "",
|
|
16
|
+
reference.from,
|
|
17
|
+
reference.to,
|
|
18
|
+
path,
|
|
19
|
+
String(line),
|
|
20
|
+
String(column),
|
|
21
|
+
reference.use,
|
|
22
|
+
].join("\u0000");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Indexes what the passes answered for each configuration of one run, in the run's order.
|
|
26
|
+
* A declaration is keyed by its rendered position, with the fields of the first
|
|
27
|
+
* configuration that judges it; one in a file every holder holds only for its references
|
|
28
|
+
* is judged by none and is not in the union, and a reference it makes enters the union as a
|
|
29
|
+
* consumer's does. One reference at one position of a file several configurations compile
|
|
30
|
+
* is one reference; two one configuration makes are two.
|
|
31
|
+
*/
|
|
32
|
+
export function matrixOf(per) {
|
|
33
|
+
const symbols = [];
|
|
34
|
+
const holders = new Map();
|
|
35
|
+
const references = [];
|
|
36
|
+
const makers = [];
|
|
37
|
+
const firstSeen = new Map();
|
|
38
|
+
const judged = new Set();
|
|
39
|
+
const unjudged = new Set();
|
|
40
|
+
const dependents = new Map();
|
|
41
|
+
per.forEach((one, index) => {
|
|
42
|
+
for (const [id, after] of one.dependents ?? []) {
|
|
43
|
+
if (!dependents.has(id)) {
|
|
44
|
+
dependents.set(id, after);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const referenceOnly = new Set(one.referenceOnly);
|
|
48
|
+
for (const symbol of one.symbols) {
|
|
49
|
+
const held = holders.get(symbol.id);
|
|
50
|
+
if (held === undefined) {
|
|
51
|
+
holders.set(symbol.id, [one.configuration]);
|
|
52
|
+
}
|
|
53
|
+
else if (!held.includes(one.configuration)) {
|
|
54
|
+
held.push(one.configuration);
|
|
55
|
+
}
|
|
56
|
+
if (referenceOnly.has(symbol.position.path)) {
|
|
57
|
+
if (swept(symbol.kind)) {
|
|
58
|
+
unjudged.add(symbol.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (!judged.has(symbol.id)) {
|
|
62
|
+
judged.add(symbol.id);
|
|
63
|
+
symbols.push(symbol);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const reference of one.references) {
|
|
67
|
+
const key = referenceKey(reference);
|
|
68
|
+
const seen = firstSeen.get(key);
|
|
69
|
+
if (seen !== undefined && seen.index !== index) {
|
|
70
|
+
for (const place of seen.places) {
|
|
71
|
+
const making = makers[place];
|
|
72
|
+
if (making !== undefined && !making.includes(one.configuration)) {
|
|
73
|
+
making.push(one.configuration);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const places = seen?.places ?? [];
|
|
79
|
+
if (seen === undefined) {
|
|
80
|
+
firstSeen.set(key, { index, places });
|
|
81
|
+
}
|
|
82
|
+
places.push(references.length);
|
|
83
|
+
references.push(reference);
|
|
84
|
+
makers.push([one.configuration]);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
for (const id of judged) {
|
|
88
|
+
unjudged.delete(id);
|
|
89
|
+
}
|
|
90
|
+
symbols.sort((a, b) => byPosition(a.position, b.position));
|
|
91
|
+
const testUsed = new Set(per.flatMap((one) => one.testUsedFiles ?? []));
|
|
92
|
+
return {
|
|
93
|
+
configurations: per.map((one) => one.configuration),
|
|
94
|
+
union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))], [...new Set(per.flatMap((one) => one.supportFiles ?? []))], new Set(per.flatMap((one) => [...(one.ambient ?? [])])), { unjudged, configurations: makers }),
|
|
95
|
+
heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
|
|
96
|
+
testUsed: symbols.map((symbol) => testUsed.has(symbol.position.path)),
|
|
97
|
+
dependents,
|
|
98
|
+
};
|
|
99
|
+
}
|