@cplieger/deadset-ts 3.4.0 → 3.6.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 +1 -1
- package/dist/src/analysis.js +70 -14
- package/dist/src/components.js +66 -107
- package/dist/src/config.js +6 -0
- package/dist/src/dependencies.js +493 -0
- package/dist/src/deprecation.js +83 -0
- package/dist/src/discover.js +1 -1
- package/dist/src/enum-group.js +217 -0
- package/dist/src/file-facts.js +101 -0
- package/dist/src/findings/boundary.js +1 -0
- package/dist/src/findings/completion.js +99 -0
- package/dist/src/findings/dependencies-and-module-machinery.js +65 -1
- package/dist/src/findings/emitters.js +8 -0
- package/dist/src/findings/interfaces.js +95 -2
- package/dist/src/findings/non-code-artifacts.js +97 -1
- package/dist/src/findings/reads-and-writes.js +199 -2
- package/dist/src/findings/unused-declarations.js +182 -1
- package/dist/src/findings/visibility-narrowing.js +239 -2
- package/dist/src/implementations.js +210 -0
- package/dist/src/inventory.js +2 -2
- package/dist/src/kinds.js +40 -0
- package/dist/src/print.js +10 -1
- package/dist/src/resolve.js +91 -10
- package/dist/src/run.js +9 -3
- package/dist/src/schema.js +37 -1
- package/dist/src/scope.js +55 -21
- package/dist/src/serialization-contract.js +109 -19
- package/dist/src/stores.js +32 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +113 -16
- package/src/components.ts +73 -131
- package/src/config.ts +37 -2
- package/src/dependencies.ts +617 -0
- package/src/deprecation.ts +101 -0
- package/src/discover.ts +1 -1
- package/src/enum-group.ts +265 -0
- package/src/file-facts.ts +143 -0
- package/src/findings/boundary.ts +39 -0
- package/src/findings/completion.ts +151 -0
- package/src/findings/dependencies-and-module-machinery.ts +79 -1
- package/src/findings/emitter.ts +21 -1
- package/src/findings/emitters.ts +13 -1
- package/src/findings/interfaces.ts +149 -3
- package/src/findings/non-code-artifacts.ts +124 -2
- package/src/findings/reads-and-writes.ts +310 -3
- package/src/findings/unused-declarations.ts +223 -2
- package/src/findings/visibility-narrowing.ts +324 -3
- package/src/implementations.ts +286 -0
- package/src/inventory.ts +2 -2
- package/src/kinds.ts +41 -0
- package/src/print.ts +17 -1
- package/src/resolve.ts +124 -10
- package/src/run.ts +10 -3
- package/src/schema.ts +47 -2
- package/src/scope.ts +63 -22
- package/src/serialization-contract.ts +138 -20
- package/src/stores.ts +77 -0
- package/src/version.ts +1 -1
package/src/analysis.ts
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
import type { Diagnostic } from "@typescript/native/unstable/sync";
|
|
12
12
|
import type { Config, Provenance } from "./config.ts";
|
|
13
13
|
import { decorator } from "./decorator.ts";
|
|
14
|
+
import { dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
15
|
+
import { deprecatedDeclarations } from "./deprecation.ts";
|
|
14
16
|
import { diagnosticErrors, discoverProjects, DiscoveryError } from "./discover.ts";
|
|
17
|
+
import { enumGroup } from "./enum-group.ts";
|
|
15
18
|
import {
|
|
16
19
|
computeExemptions,
|
|
17
20
|
disabledClasses,
|
|
@@ -21,9 +24,12 @@ import {
|
|
|
21
24
|
type Detectors,
|
|
22
25
|
} from "./exempt.ts";
|
|
23
26
|
import type { TSExemptionClass } from "./exempt-classes.ts";
|
|
27
|
+
import { heldByInclusion, sourceTree } from "./file-facts.ts";
|
|
24
28
|
import type { Finding } from "./finding.ts";
|
|
29
|
+
import type { EmitterInput } from "./findings/emitter.ts";
|
|
25
30
|
import { frameworkLifecycle } from "./framework-lifecycle.ts";
|
|
26
31
|
import type { Host } from "./host.ts";
|
|
32
|
+
import { implementations, mergeImplementations } from "./implementations.ts";
|
|
27
33
|
import { injectionContainer } from "./injection-container.ts";
|
|
28
34
|
import { interfaceSatisfaction } from "./interface-satisfaction.ts";
|
|
29
35
|
import { inventory, type Inventory } from "./inventory.ts";
|
|
@@ -37,6 +43,7 @@ import { roots, unmatchedEverywhere, unmatchedRoots, type RootKind, type Roots }
|
|
|
37
43
|
import type { Scope } from "./scope.ts";
|
|
38
44
|
import { serializationContract } from "./serialization-contract.ts";
|
|
39
45
|
import { diagnosticsOf, runSession, type Engine, type ProjectView } from "./session.ts";
|
|
46
|
+
import { accessorsOf, storesOf } from "./stores.ts";
|
|
40
47
|
import type { Exemption, SweepInput } from "./sweep.ts";
|
|
41
48
|
import { readTemplates, templateField } from "./template-field.ts";
|
|
42
49
|
|
|
@@ -222,6 +229,7 @@ export interface RunSweep {
|
|
|
222
229
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
223
230
|
const DETECTORS: Detectors = new Map<TSExemptionClass, Detector>([
|
|
224
231
|
["interface-satisfaction", interfaceSatisfaction],
|
|
232
|
+
["enum-group", enumGroup],
|
|
225
233
|
["template-field", templateField],
|
|
226
234
|
["reflective-lookup", reflectiveLookup],
|
|
227
235
|
["decorator", decorator],
|
|
@@ -230,31 +238,40 @@ const DETECTORS: Detectors = new Map<TSExemptionClass, Detector>([
|
|
|
230
238
|
["serialization-contract", serializationContract],
|
|
231
239
|
]);
|
|
232
240
|
|
|
241
|
+
/** What the sweep of one run reads of each project, beside what a caller's stage adds. */
|
|
242
|
+
interface SweptProject<Extra> {
|
|
243
|
+
readonly configured: Configured;
|
|
244
|
+
readonly exempt: readonly Exemption[];
|
|
245
|
+
readonly extra: Extra;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** The run's sweep, the exemption records it was swept with, and each project's extra answer. */
|
|
249
|
+
interface SweptRun<Extra> {
|
|
250
|
+
readonly swept: RunSweep;
|
|
251
|
+
readonly exempt: readonly Exemption[];
|
|
252
|
+
readonly projects: readonly SweptProject<Extra>[];
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
/**
|
|
234
|
-
* Sweeps the run
|
|
235
|
-
*
|
|
236
|
-
* projects then merge into one matrix in the run's order, which is swept under the
|
|
237
|
-
* caller's marks and mode with the exemptions every project found. A record found in
|
|
238
|
-
* one configuration holds in every configuration that holds its declaration, because
|
|
239
|
-
* an exemption is evidence of a use the analysis cannot see and a use in one
|
|
240
|
-
* configuration is a use. A project that carries an error ends the run before any
|
|
241
|
-
* project is swept.
|
|
256
|
+
* Sweeps the run, reading each project's references and exemptions, and `extra`'s
|
|
257
|
+
* answer, while the project's view is open.
|
|
242
258
|
*/
|
|
243
|
-
|
|
259
|
+
function sweepRun<Extra>(
|
|
244
260
|
engine: Engine,
|
|
245
261
|
host: Host,
|
|
246
262
|
scope: Scope,
|
|
247
263
|
config: Config,
|
|
248
264
|
input: Pick<SweepInput, "marked" | "mode">,
|
|
249
|
-
detectors: Detectors
|
|
250
|
-
|
|
265
|
+
detectors: Detectors,
|
|
266
|
+
extra: <Brand>(project: ProjectView<Brand>, read: ProjectRead) => Extra,
|
|
267
|
+
): SweptRun<Extra> {
|
|
251
268
|
const targetRoot = scope.target.path;
|
|
252
269
|
const disabled = disabledClasses(config);
|
|
253
270
|
const templates = disabled.has("template-field")
|
|
254
271
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
255
272
|
: readTemplates(host, targetRoot, config.analysis);
|
|
256
273
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
257
|
-
const
|
|
274
|
+
const projects = readProjects(engine, host, scope, config, (project, projectRead) => {
|
|
258
275
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
259
276
|
testFiles: config.ts.testFiles,
|
|
260
277
|
});
|
|
@@ -274,9 +291,89 @@ export function runSweep(
|
|
|
274
291
|
testFiles: new Set(resolved.testFilePaths),
|
|
275
292
|
},
|
|
276
293
|
);
|
|
277
|
-
return { configured, exempt };
|
|
294
|
+
return { configured, exempt, extra: extra(project, projectRead) };
|
|
278
295
|
});
|
|
279
|
-
const matrix = matrixOf(
|
|
280
|
-
const
|
|
281
|
-
|
|
296
|
+
const matrix = matrixOf(projects.map((one) => one.configured));
|
|
297
|
+
const exempt = exemptionsOf(projects.flatMap((one) => one.exempt));
|
|
298
|
+
const swept: SweepInput = { ...input, exempt };
|
|
299
|
+
return {
|
|
300
|
+
swept: { matrix, sweep: sweepMatrix(matrix, swept), retained: retainedIn(matrix, swept) },
|
|
301
|
+
exempt,
|
|
302
|
+
projects,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Sweeps the run the scope and the configuration describe. Each project's references
|
|
308
|
+
* and exemptions are read against its own inventory while its view is open; the
|
|
309
|
+
* projects then merge into one matrix in the run's order, which is swept under the
|
|
310
|
+
* caller's marks and mode with the exemptions every project found. A record found in
|
|
311
|
+
* one configuration holds in every configuration that holds its declaration, because
|
|
312
|
+
* an exemption is evidence of a use the analysis cannot see and a use in one
|
|
313
|
+
* configuration is a use. A project that carries an error ends the run before any
|
|
314
|
+
* project is swept.
|
|
315
|
+
*/
|
|
316
|
+
export function runSweep(
|
|
317
|
+
engine: Engine,
|
|
318
|
+
host: Host,
|
|
319
|
+
scope: Scope,
|
|
320
|
+
config: Config,
|
|
321
|
+
input: Pick<SweepInput, "marked" | "mode">,
|
|
322
|
+
detectors: Detectors = DETECTORS,
|
|
323
|
+
): RunSweep {
|
|
324
|
+
return sweepRun(engine, host, scope, config, input, detectors, () => undefined).swept;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* What every emitter reads of the run the scope and the configuration describe: the
|
|
329
|
+
* run's sweep, and the facts beside it each kind family reads, every per-project one
|
|
330
|
+
* read in the same session as the sweep. The run loads no consumer and reads no
|
|
331
|
+
* declared cross-language edge, so its boundary names neither.
|
|
332
|
+
*/
|
|
333
|
+
export function runEmitterInput(
|
|
334
|
+
engine: Engine,
|
|
335
|
+
host: Host,
|
|
336
|
+
scope: Scope,
|
|
337
|
+
config: Config,
|
|
338
|
+
input: Pick<SweepInput, "marked" | "mode">,
|
|
339
|
+
detectors: Detectors = DETECTORS,
|
|
340
|
+
): EmitterInput {
|
|
341
|
+
const targetRoot = scope.target.path;
|
|
342
|
+
const run = sweepRun(engine, host, scope, config, input, detectors, (project, read) => ({
|
|
343
|
+
deprecated: deprecatedDeclarations(project, read.held),
|
|
344
|
+
accessors: accessorsOf(project, read.held),
|
|
345
|
+
needs: projectNeeds(project, read.held, host),
|
|
346
|
+
implementations: implementations(project, read.held),
|
|
347
|
+
heldByInclusion: heldByInclusion(project, targetRoot),
|
|
348
|
+
}));
|
|
349
|
+
const { swept, projects } = run;
|
|
350
|
+
return {
|
|
351
|
+
config,
|
|
352
|
+
swept,
|
|
353
|
+
deprecated: new Set(projects.flatMap((one) => one.extra.deprecated)),
|
|
354
|
+
stores: storesOf(
|
|
355
|
+
projects.map((one) => ({
|
|
356
|
+
references: one.configured.references,
|
|
357
|
+
accessors: one.extra.accessors,
|
|
358
|
+
})),
|
|
359
|
+
run.exempt,
|
|
360
|
+
input.mode,
|
|
361
|
+
),
|
|
362
|
+
dependencies: dependenciesOf(
|
|
363
|
+
host,
|
|
364
|
+
targetRoot,
|
|
365
|
+
projects.map((one) => one.extra.needs),
|
|
366
|
+
swept.sweep.candidates.map((candidate) => candidate.id),
|
|
367
|
+
),
|
|
368
|
+
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
369
|
+
files: {
|
|
370
|
+
tree: sourceTree(host, targetRoot),
|
|
371
|
+
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
372
|
+
},
|
|
373
|
+
boundary: {
|
|
374
|
+
consumers: { declared: scope.consumers.map((consumer) => consumer.id), loaded: [] },
|
|
375
|
+
encapsulated: readManifest(host, targetRoot).declaresExports,
|
|
376
|
+
edges: [],
|
|
377
|
+
},
|
|
378
|
+
};
|
|
282
379
|
}
|
package/src/components.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The dead components of one graph
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The dead components of one graph. A component is a set of dead declarations one
|
|
3
|
+
* deletion removes together: its root members, which are where the deletion starts,
|
|
4
|
+
* and every dead declaration that falls with them, being dead only through them.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Cascade } from "./config.ts";
|
|
@@ -14,28 +14,30 @@ const ANALYZER = "deadset-ts";
|
|
|
14
14
|
/** The fewest digits a component identifier's number is written with. */
|
|
15
15
|
const ID_DIGITS = 4;
|
|
16
16
|
|
|
17
|
+
/** The identifier of the component at one place of the run's order, counted from one. */
|
|
18
|
+
export function componentId(place: number): string {
|
|
19
|
+
return `${ANALYZER}/c-${String(place).padStart(ID_DIGITS, "0")}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
/** The index of a declaration the component walk has not reached. */
|
|
18
23
|
const UNVISITED = -1;
|
|
19
24
|
|
|
20
|
-
/** One dead component
|
|
25
|
+
/** One dead component. */
|
|
21
26
|
export interface Component {
|
|
22
27
|
/** The analyzer's name, a solidus, and `c-` followed by the component's place in the order. */
|
|
23
28
|
readonly id: string;
|
|
24
|
-
/** The component's declarations, by site. A dead member of a dead container is one of them. */
|
|
25
|
-
readonly members: readonly string[];
|
|
26
29
|
/**
|
|
27
|
-
* The
|
|
28
|
-
*
|
|
29
|
-
* component outside it references.
|
|
30
|
+
* The component's declarations by site: its root members and every dead declaration
|
|
31
|
+
* that falls with them. A dead member of a dead container is one of them.
|
|
30
32
|
*/
|
|
31
|
-
readonly
|
|
33
|
+
readonly members: readonly string[];
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* The root members, by site. A root member is a member of a cycle no dead declaration
|
|
36
|
+
* outside the cycle references, whose container is not dead; every member of such a
|
|
37
|
+
* cycle with a live container is one, so a component has at least one.
|
|
36
38
|
*/
|
|
37
|
-
readonly
|
|
38
|
-
/** The source lines
|
|
39
|
+
readonly roots: readonly string[];
|
|
40
|
+
/** The source lines one deletion of the component removes: the distinct lines its members span. */
|
|
39
41
|
readonly deletableLines: number;
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -48,22 +50,22 @@ export interface Listing {
|
|
|
48
50
|
readonly deletableLines: number;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
/** What a report carries for one component: its roots and
|
|
53
|
+
/** What a report carries for one component: its roots and counts, and its members in full. */
|
|
52
54
|
export function listing(component: Component, cascade: Cascade): Listing {
|
|
53
55
|
return {
|
|
54
56
|
roots: component.roots,
|
|
55
57
|
members: cascade === "full" ? component.members : [],
|
|
56
|
-
symbolCount: component.
|
|
58
|
+
symbolCount: component.members.length,
|
|
57
59
|
deletableLines: component.deletableLines,
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
|
-
* Groups the declarations `dead` marks into components
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* Groups the declarations `dead` marks into components, ordered by their first member's
|
|
65
|
+
* site; `dead` and `testOfDeadCode` hold one flag per declaration of the graph. Root
|
|
66
|
+
* members sit on the cycles no other cycle references. A cycle two roots both reach is
|
|
67
|
+
* dead only through both, so a component is a weakly connected part of the dead
|
|
68
|
+
* subgraph: no dead declaration of one references a declaration of another.
|
|
67
69
|
*/
|
|
68
70
|
export function componentsOf(
|
|
69
71
|
graph: Graph,
|
|
@@ -75,26 +77,22 @@ export function componentsOf(
|
|
|
75
77
|
return [];
|
|
76
78
|
}
|
|
77
79
|
const { componentOf, members } = connected(adjacent);
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const outsideReference = externallyReferenced(adjacent, componentOf);
|
|
80
|
+
const referenced = referencedCycles(adjacent, componentOf, members.length);
|
|
81
|
+
const groups = joined(adjacent);
|
|
81
82
|
const idOf = (position: number): string => graph.symbols[at[position] ?? 0]?.id ?? "";
|
|
82
83
|
|
|
83
|
-
return
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
// deletion starts and the member falls with it.
|
|
84
|
+
return groups.map((group, place) => {
|
|
85
|
+
const roots = group.filter((position) => {
|
|
86
|
+
// A dead member's dead container is on the member's own cycle, and the container
|
|
87
|
+
// is where the deletion starts.
|
|
88
88
|
const container = graph.parent[at[position] ?? 0] ?? OUTSIDE;
|
|
89
|
-
return
|
|
89
|
+
return referenced[componentOf[position] ?? 0] !== true && dead[container] !== true;
|
|
90
90
|
});
|
|
91
|
-
const fallen = falls[group] ?? [];
|
|
92
91
|
return {
|
|
93
|
-
id:
|
|
94
|
-
members:
|
|
92
|
+
id: componentId(place + 1),
|
|
93
|
+
members: group.map(idOf),
|
|
95
94
|
roots: roots.map(idOf),
|
|
96
|
-
|
|
97
|
-
deletableLines: linesSpanned(fallen.map((position) => graph.symbols[at[position] ?? 0])),
|
|
95
|
+
deletableLines: linesSpanned(group.map((position) => graph.symbols[at[position] ?? 0])),
|
|
98
96
|
};
|
|
99
97
|
});
|
|
100
98
|
}
|
|
@@ -185,10 +183,9 @@ interface Frame {
|
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
/**
|
|
188
|
-
* Each declaration's
|
|
186
|
+
* Each declaration's cycle and each cycle's members by site, by Tarjan's
|
|
189
187
|
* algorithm in one pass. The walk keeps its own stack of frames, so a chain of any length
|
|
190
|
-
* is walked without growing the call stack.
|
|
191
|
-
* each follows every component it reaches.
|
|
188
|
+
* is walked without growing the call stack.
|
|
192
189
|
*/
|
|
193
190
|
function connected(adjacent: readonly (readonly number[])[]): {
|
|
194
191
|
readonly componentOf: readonly number[];
|
|
@@ -257,45 +254,18 @@ function connected(adjacent: readonly (readonly number[])[]): {
|
|
|
257
254
|
return { componentOf, members };
|
|
258
255
|
}
|
|
259
256
|
|
|
260
|
-
/**
|
|
261
|
-
|
|
262
|
-
* it, each pair counted once. An edge inside a component is not an edge between two, so
|
|
263
|
-
* the result is acyclic.
|
|
264
|
-
*/
|
|
265
|
-
function condense(
|
|
257
|
+
/** Per cycle, whether a dead declaration outside it references one of its members. */
|
|
258
|
+
function referencedCycles(
|
|
266
259
|
adjacent: readonly (readonly number[])[],
|
|
267
260
|
componentOf: readonly number[],
|
|
268
261
|
count: number,
|
|
269
|
-
): { readonly edges: readonly (readonly number[])[]; readonly into: readonly number[] } {
|
|
270
|
-
const edges: number[][] = Array.from({ length: count }, () => []);
|
|
271
|
-
const into = Array.from({ length: count }, () => 0);
|
|
272
|
-
const seen = new Set<string>();
|
|
273
|
-
adjacent.forEach((targets, from) => {
|
|
274
|
-
for (const to of targets) {
|
|
275
|
-
const a = componentOf[from] ?? 0;
|
|
276
|
-
const b = componentOf[to] ?? 0;
|
|
277
|
-
const pair = `${String(a)}>${String(b)}`;
|
|
278
|
-
if (a === b || seen.has(pair)) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
seen.add(pair);
|
|
282
|
-
edges[a]?.push(b);
|
|
283
|
-
into[b] = (into[b] ?? 0) + 1;
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
return { edges, into };
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** Per dead declaration, whether a dead component other than its own references it. */
|
|
290
|
-
function externallyReferenced(
|
|
291
|
-
adjacent: readonly (readonly number[])[],
|
|
292
|
-
componentOf: readonly number[],
|
|
293
262
|
): readonly boolean[] {
|
|
294
|
-
const referenced =
|
|
263
|
+
const referenced = Array.from({ length: count }, () => false);
|
|
295
264
|
adjacent.forEach((targets, from) => {
|
|
296
265
|
for (const to of targets) {
|
|
297
|
-
|
|
298
|
-
|
|
266
|
+
const cycle = componentOf[to] ?? 0;
|
|
267
|
+
if (componentOf[from] !== cycle) {
|
|
268
|
+
referenced[cycle] = true;
|
|
299
269
|
}
|
|
300
270
|
}
|
|
301
271
|
});
|
|
@@ -303,70 +273,42 @@ function externallyReferenced(
|
|
|
303
273
|
}
|
|
304
274
|
|
|
305
275
|
/**
|
|
306
|
-
* The
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*/
|
|
310
|
-
function order(
|
|
311
|
-
edges: readonly (readonly number[])[],
|
|
312
|
-
members: readonly (readonly number[])[],
|
|
313
|
-
): readonly number[] {
|
|
314
|
-
const sequence = edges.map((_targets, component) => component).reverse();
|
|
315
|
-
const depth = edges.map(() => 0);
|
|
316
|
-
for (const from of sequence) {
|
|
317
|
-
for (const to of edges[from] ?? []) {
|
|
318
|
-
depth[to] = Math.max(depth[to] ?? 0, (depth[from] ?? 0) + 1);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return sequence.sort(
|
|
322
|
-
(a, b) => (depth[a] ?? 0) - (depth[b] ?? 0) || (members[a]?.[0] ?? 0) - (members[b]?.[0] ?? 0),
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Per component, the dead declarations one deletion of it removes. A root of the
|
|
328
|
-
* condensation carries its members and every component only it reaches; any other
|
|
329
|
-
* component carries its own members alone, because what it reaches is reached through a
|
|
330
|
-
* root above it as well.
|
|
276
|
+
* The weakly connected parts of the dead subgraph, each its positions in ascending
|
|
277
|
+
* order, ordered by their first position. Positions follow the graph's declaration
|
|
278
|
+
* order, so a part's first position is its first member's site.
|
|
331
279
|
*/
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// share one array rather than allocating one per root.
|
|
339
|
-
const stamp = edges.map(() => UNVISITED);
|
|
340
|
-
const reach = new Map<number, readonly number[]>();
|
|
341
|
-
const owners = edges.map(() => 0);
|
|
342
|
-
edges.forEach((_targets, root) => {
|
|
343
|
-
if (into[root] !== 0) {
|
|
344
|
-
return;
|
|
280
|
+
function joined(adjacent: readonly (readonly number[])[]): readonly (readonly number[])[] {
|
|
281
|
+
const leader = adjacent.map((_edges, position) => position);
|
|
282
|
+
const find = (position: number): number => {
|
|
283
|
+
let top = position;
|
|
284
|
+
while ((leader[top] ?? top) !== top) {
|
|
285
|
+
top = leader[top] ?? top;
|
|
345
286
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
for (const to of edges[at] ?? []) {
|
|
352
|
-
if (stamp[to] !== root) {
|
|
353
|
-
stamp[to] = root;
|
|
354
|
-
reached.push(to);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
287
|
+
// Every position on the walk points at the top afterwards, so a later find is short.
|
|
288
|
+
for (let step = position; step !== top;) {
|
|
289
|
+
const next = leader[step] ?? top;
|
|
290
|
+
leader[step] = top;
|
|
291
|
+
step = next;
|
|
357
292
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
293
|
+
return top;
|
|
294
|
+
};
|
|
295
|
+
adjacent.forEach((targets, from) => {
|
|
296
|
+
for (const to of targets) {
|
|
297
|
+
const a = find(from);
|
|
298
|
+
const b = find(to);
|
|
299
|
+
// The smaller position leads, so a part's leader is its first position.
|
|
300
|
+
leader[Math.max(a, b)] = Math.min(a, b);
|
|
361
301
|
}
|
|
362
302
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
303
|
+
const parts = new Map<number, number[]>();
|
|
304
|
+
adjacent.forEach((_edges, position) => {
|
|
305
|
+
const top = find(position);
|
|
306
|
+
const part = parts.get(top);
|
|
307
|
+
if (part === undefined) {
|
|
308
|
+
parts.set(top, [position]);
|
|
309
|
+
} else {
|
|
310
|
+
part.push(position);
|
|
369
311
|
}
|
|
370
|
-
return set.sort((a, b) => a - b);
|
|
371
312
|
});
|
|
313
|
+
return [...parts.values()];
|
|
372
314
|
}
|
package/src/config.ts
CHANGED
|
@@ -122,6 +122,34 @@ export interface LifecycleContract {
|
|
|
122
122
|
readonly members: readonly string[];
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* One analyzer the orchestrator may run, in exactly one of two shapes: an installed
|
|
127
|
+
* analyzer, named by its command alone, or an acquirable one, which also names the
|
|
128
|
+
* artifact acquisition fetches and verifies. This analyzer validates and prints the
|
|
129
|
+
* list and runs nothing from it.
|
|
130
|
+
*/
|
|
131
|
+
export type Provider =
|
|
132
|
+
| {
|
|
133
|
+
readonly shape: "installed";
|
|
134
|
+
readonly name: string;
|
|
135
|
+
readonly languages: readonly Language[];
|
|
136
|
+
readonly command: string;
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
readonly shape: "acquirable";
|
|
140
|
+
readonly name: string;
|
|
141
|
+
readonly languages: readonly Language[];
|
|
142
|
+
readonly command: string;
|
|
143
|
+
readonly source: string;
|
|
144
|
+
readonly version: string;
|
|
145
|
+
readonly digest: string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** The analyzers the orchestrator runs. */
|
|
149
|
+
export interface Providers {
|
|
150
|
+
readonly analyzers: readonly Provider[];
|
|
151
|
+
}
|
|
152
|
+
|
|
125
153
|
/** The section the TypeScript analyzer owns. */
|
|
126
154
|
export interface TSSection {
|
|
127
155
|
readonly testFiles: readonly string[];
|
|
@@ -152,6 +180,7 @@ export interface Config {
|
|
|
152
180
|
readonly severity: ReadonlyMap<string, Severity>;
|
|
153
181
|
readonly exemptionsDisabled: readonly string[];
|
|
154
182
|
readonly reporters: Reporters;
|
|
183
|
+
readonly providers: Providers;
|
|
155
184
|
readonly ts: TSSection;
|
|
156
185
|
}
|
|
157
186
|
|
|
@@ -184,6 +213,12 @@ export function defaultConfig(): Config {
|
|
|
184
213
|
maxFindings: 0,
|
|
185
214
|
failOn: "deny",
|
|
186
215
|
},
|
|
216
|
+
providers: {
|
|
217
|
+
analyzers: [
|
|
218
|
+
{ shape: "installed", name: "deadset-go", languages: ["go"], command: "deadset-go" },
|
|
219
|
+
{ shape: "installed", name: "deadset-ts", languages: ["ts"], command: "deadset-ts" },
|
|
220
|
+
],
|
|
221
|
+
},
|
|
187
222
|
ts: {
|
|
188
223
|
testFiles: ["**/*.test.{ts,tsx,mts,cts}"],
|
|
189
224
|
entryFiles: [],
|
|
@@ -246,8 +281,8 @@ export type ErrorKind =
|
|
|
246
281
|
*/
|
|
247
282
|
| "malformed"
|
|
248
283
|
/**
|
|
249
|
-
* A key the closed key list does not declare, or a severity key naming
|
|
250
|
-
* whose severity the Contract fixes.
|
|
284
|
+
* A key the closed key list does not declare, or a severity key naming no live
|
|
285
|
+
* kind or a kind whose severity the Contract fixes.
|
|
251
286
|
*/
|
|
252
287
|
| "unimplemented-key"
|
|
253
288
|
/** A resolved configuration no source supplied a target kind for. */
|