@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/README.md
CHANGED
|
@@ -8,7 +8,7 @@ deadset-ts is a command-line analyzer for TypeScript and JavaScript projects. It
|
|
|
8
8
|
|
|
9
9
|
deadset-ts is report-only. It never edits source, and it refuses any `--fix` flag.
|
|
10
10
|
|
|
11
|
-
**Status: pre-release.** This version implements contract 3.
|
|
11
|
+
**Status: pre-release.** This version implements contract 3.2.0 and ships the analyzer's foundation: one compiler session per run over the projects `analysis.configurations` declares, in the order it lists them, each a compiler configuration file below the target, or, where it declares none, every `tsconfig` project discovered under the target (the explicit scope, every `tsconfig*.json`, each project's `references`), a fail-closed read of every project's diagnostics, configuration decoding against the contract's closed key list with `print-config` and its provenance, `print-projects`, the report-only guard, the symbol inventory of every declaration a project's own files hold down to its class and type members, the one-pass reference resolution over that inventory, which resolves each file's identifiers in batches ([batch-cap calibration](docs/batch-cap-calibration.md) is the measurement behind the batch size), and the root set `print-roots` prints. A root is a declaration the analysis keeps live without a reference: what a file exports when the manifest names it through `main`, `module`, `types`, `bin` or `exports`, or when `ts.entry_files` matches it; a library target's published API; the configuration, setup and test files of Vitest, Stryker and Playwright, the flat configuration of ESLint, and a worker or service worker a call addresses by a string literal; and every declaration a `roots.patterns` entry names. An entry that names nothing is reported as `DS1704` and fails the run. The entry-point conventions of other tools are not read, and `DECLINED_CONVENTIONS` lists each one with the reason. Seven exemption classes hold a declaration back from the sweep, and `print-retained` lists what each one held back: `interface-satisfaction`, a member an interface requires of a class whose value reaches that interface; `decorator`, a decorated member and every member of a decorated class; `injection-container`, a decorated property of a class a dependency-injection container constructs, being a class passed to a call `ts.injection_registrations` names or one a parameter of whose constructor carries a decorator; `framework-lifecycle`, a member a `ts.lifecycle_contracts` entry lists, on a class that is the entry's component by a decorator or a call the entry names or by extending a class it names; `serialization-contract`, the properties of a class whose values reach `JSON.stringify` or a declaration `ts.serializers` names, and those and its `toJSON` and `toString` where a value is passed to an `unknown` or `any` parameter of a function outside the analysis; and, at the lowest confidence, `template-field`, a member an action of a template under `analysis.template_dirs` names, and `reflective-lookup`, a member a literal key of an element access or of `Reflect.get`, `Reflect.set` or `Reflect.has` names. None of them holds back a `#private` member. The analysis is reached through the command, and through `run()` for a caller that runs the command in-process; no other verb reports findings yet, so `analyze`, `explain` and `describe` exit with a usage message. The one runtime dependency is the `typescript` package at exactly 7.0.2, installed under the `@typescript/native` name until TypeScript 7.1 ships a compiler API the tooling can share.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
package/dist/src/analysis.js
CHANGED
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
* project's checker, and that holds only for views the session itself hands out.
|
|
9
9
|
*/
|
|
10
10
|
import { decorator } from "./decorator.js";
|
|
11
|
+
import { dependenciesOf, projectNeeds } from "./dependencies.js";
|
|
12
|
+
import { deprecatedDeclarations } from "./deprecation.js";
|
|
11
13
|
import { diagnosticErrors, discoverProjects, DiscoveryError } from "./discover.js";
|
|
14
|
+
import { enumGroup } from "./enum-group.js";
|
|
12
15
|
import { computeExemptions, disabledClasses, exemptionsOf, retainedIn, } from "./exempt.js";
|
|
16
|
+
import { heldByInclusion, sourceTree } from "./file-facts.js";
|
|
13
17
|
import { frameworkLifecycle } from "./framework-lifecycle.js";
|
|
18
|
+
import { implementations, mergeImplementations } from "./implementations.js";
|
|
14
19
|
import { injectionContainer } from "./injection-container.js";
|
|
15
20
|
import { interfaceSatisfaction } from "./interface-satisfaction.js";
|
|
16
21
|
import { inventory } from "./inventory.js";
|
|
@@ -23,6 +28,7 @@ import { references } from "./references.js";
|
|
|
23
28
|
import { roots, unmatchedEverywhere, unmatchedRoots } from "./roots.js";
|
|
24
29
|
import { serializationContract } from "./serialization-contract.js";
|
|
25
30
|
import { diagnosticsOf, runSession } from "./session.js";
|
|
31
|
+
import { accessorsOf, storesOf } from "./stores.js";
|
|
26
32
|
import { readTemplates, templateField } from "./template-field.js";
|
|
27
33
|
/** The setting whose source decides where a finding about a configured root sits. */
|
|
28
34
|
const ROOTS_SETTING = "roots.patterns";
|
|
@@ -128,6 +134,7 @@ export function runRoots(engine, host, scope, config, provenance) {
|
|
|
128
134
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
129
135
|
const DETECTORS = new Map([
|
|
130
136
|
["interface-satisfaction", interfaceSatisfaction],
|
|
137
|
+
["enum-group", enumGroup],
|
|
131
138
|
["template-field", templateField],
|
|
132
139
|
["reflective-lookup", reflectiveLookup],
|
|
133
140
|
["decorator", decorator],
|
|
@@ -136,23 +143,17 @@ const DETECTORS = new Map([
|
|
|
136
143
|
["serialization-contract", serializationContract],
|
|
137
144
|
]);
|
|
138
145
|
/**
|
|
139
|
-
* Sweeps the run
|
|
140
|
-
*
|
|
141
|
-
* projects then merge into one matrix in the run's order, which is swept under the
|
|
142
|
-
* caller's marks and mode with the exemptions every project found. A record found in
|
|
143
|
-
* one configuration holds in every configuration that holds its declaration, because
|
|
144
|
-
* an exemption is evidence of a use the analysis cannot see and a use in one
|
|
145
|
-
* configuration is a use. A project that carries an error ends the run before any
|
|
146
|
-
* project is swept.
|
|
146
|
+
* Sweeps the run, reading each project's references and exemptions, and `extra`'s
|
|
147
|
+
* answer, while the project's view is open.
|
|
147
148
|
*/
|
|
148
|
-
|
|
149
|
+
function sweepRun(engine, host, scope, config, input, detectors, extra) {
|
|
149
150
|
const targetRoot = scope.target.path;
|
|
150
151
|
const disabled = disabledClasses(config);
|
|
151
152
|
const templates = disabled.has("template-field")
|
|
152
153
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
153
154
|
: readTemplates(host, targetRoot, config.analysis);
|
|
154
155
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
155
|
-
const
|
|
156
|
+
const projects = readProjects(engine, host, scope, config, (project, projectRead) => {
|
|
156
157
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
157
158
|
testFiles: config.ts.testFiles,
|
|
158
159
|
});
|
|
@@ -168,9 +169,64 @@ export function runSweep(engine, host, scope, config, input, detectors = DETECTO
|
|
|
168
169
|
mode: input.mode,
|
|
169
170
|
testFiles: new Set(resolved.testFilePaths),
|
|
170
171
|
});
|
|
171
|
-
return { configured, exempt };
|
|
172
|
+
return { configured, exempt, extra: extra(project, projectRead) };
|
|
172
173
|
});
|
|
173
|
-
const matrix = matrixOf(
|
|
174
|
-
const
|
|
175
|
-
|
|
174
|
+
const matrix = matrixOf(projects.map((one) => one.configured));
|
|
175
|
+
const exempt = exemptionsOf(projects.flatMap((one) => one.exempt));
|
|
176
|
+
const swept = { ...input, exempt };
|
|
177
|
+
return {
|
|
178
|
+
swept: { matrix, sweep: sweepMatrix(matrix, swept), retained: retainedIn(matrix, swept) },
|
|
179
|
+
exempt,
|
|
180
|
+
projects,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Sweeps the run the scope and the configuration describe. Each project's references
|
|
185
|
+
* and exemptions are read against its own inventory while its view is open; the
|
|
186
|
+
* projects then merge into one matrix in the run's order, which is swept under the
|
|
187
|
+
* caller's marks and mode with the exemptions every project found. A record found in
|
|
188
|
+
* one configuration holds in every configuration that holds its declaration, because
|
|
189
|
+
* an exemption is evidence of a use the analysis cannot see and a use in one
|
|
190
|
+
* configuration is a use. A project that carries an error ends the run before any
|
|
191
|
+
* project is swept.
|
|
192
|
+
*/
|
|
193
|
+
export function runSweep(engine, host, scope, config, input, detectors = DETECTORS) {
|
|
194
|
+
return sweepRun(engine, host, scope, config, input, detectors, () => undefined).swept;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* What every emitter reads of the run the scope and the configuration describe: the
|
|
198
|
+
* run's sweep, and the facts beside it each kind family reads, every per-project one
|
|
199
|
+
* read in the same session as the sweep. The run loads no consumer and reads no
|
|
200
|
+
* declared cross-language edge, so its boundary names neither.
|
|
201
|
+
*/
|
|
202
|
+
export function runEmitterInput(engine, host, scope, config, input, detectors = DETECTORS) {
|
|
203
|
+
const targetRoot = scope.target.path;
|
|
204
|
+
const run = sweepRun(engine, host, scope, config, input, detectors, (project, read) => ({
|
|
205
|
+
deprecated: deprecatedDeclarations(project, read.held),
|
|
206
|
+
accessors: accessorsOf(project, read.held),
|
|
207
|
+
needs: projectNeeds(project, read.held, host),
|
|
208
|
+
implementations: implementations(project, read.held),
|
|
209
|
+
heldByInclusion: heldByInclusion(project, targetRoot),
|
|
210
|
+
}));
|
|
211
|
+
const { swept, projects } = run;
|
|
212
|
+
return {
|
|
213
|
+
config,
|
|
214
|
+
swept,
|
|
215
|
+
deprecated: new Set(projects.flatMap((one) => one.extra.deprecated)),
|
|
216
|
+
stores: storesOf(projects.map((one) => ({
|
|
217
|
+
references: one.configured.references,
|
|
218
|
+
accessors: one.extra.accessors,
|
|
219
|
+
})), run.exempt, input.mode),
|
|
220
|
+
dependencies: dependenciesOf(host, targetRoot, projects.map((one) => one.extra.needs), swept.sweep.candidates.map((candidate) => candidate.id)),
|
|
221
|
+
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
222
|
+
files: {
|
|
223
|
+
tree: sourceTree(host, targetRoot),
|
|
224
|
+
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
225
|
+
},
|
|
226
|
+
boundary: {
|
|
227
|
+
consumers: { declared: scope.consumers.map((consumer) => consumer.id), loaded: [] },
|
|
228
|
+
encapsulated: readManifest(host, targetRoot).declaresExports,
|
|
229
|
+
edges: [],
|
|
230
|
+
},
|
|
231
|
+
};
|
|
176
232
|
}
|
package/dist/src/components.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
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
|
import { OUTSIDE } from "./graph.js";
|
|
7
7
|
/** The name every component identifier this analyzer mints starts with. */
|
|
8
8
|
const ANALYZER = "deadset-ts";
|
|
9
9
|
/** The fewest digits a component identifier's number is written with. */
|
|
10
10
|
const ID_DIGITS = 4;
|
|
11
|
+
/** The identifier of the component at one place of the run's order, counted from one. */
|
|
12
|
+
export function componentId(place) {
|
|
13
|
+
return `${ANALYZER}/c-${String(place).padStart(ID_DIGITS, "0")}`;
|
|
14
|
+
}
|
|
11
15
|
/** The index of a declaration the component walk has not reached. */
|
|
12
16
|
const UNVISITED = -1;
|
|
13
|
-
/** What a report carries for one component: its roots and
|
|
17
|
+
/** What a report carries for one component: its roots and counts, and its members in full. */
|
|
14
18
|
export function listing(component, cascade) {
|
|
15
19
|
return {
|
|
16
20
|
roots: component.roots,
|
|
17
21
|
members: cascade === "full" ? component.members : [],
|
|
18
|
-
symbolCount: component.
|
|
22
|
+
symbolCount: component.members.length,
|
|
19
23
|
deletableLines: component.deletableLines,
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
|
-
* Groups the declarations `dead` marks into components
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* Groups the declarations `dead` marks into components, ordered by their first member's
|
|
28
|
+
* site; `dead` and `testOfDeadCode` hold one flag per declaration of the graph. Root
|
|
29
|
+
* members sit on the cycles no other cycle references. A cycle two roots both reach is
|
|
30
|
+
* dead only through both, so a component is a weakly connected part of the dead
|
|
31
|
+
* subgraph: no dead declaration of one references a declaration of another.
|
|
28
32
|
*/
|
|
29
33
|
export function componentsOf(graph, dead, testOfDeadCode) {
|
|
30
34
|
const { at, adjacent } = deadSubgraph(graph, dead, testOfDeadCode);
|
|
@@ -32,25 +36,21 @@ export function componentsOf(graph, dead, testOfDeadCode) {
|
|
|
32
36
|
return [];
|
|
33
37
|
}
|
|
34
38
|
const { componentOf, members } = connected(adjacent);
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const outsideReference = externallyReferenced(adjacent, componentOf);
|
|
39
|
+
const referenced = referencedCycles(adjacent, componentOf, members.length);
|
|
40
|
+
const groups = joined(adjacent);
|
|
38
41
|
const idOf = (position) => graph.symbols[at[position] ?? 0]?.id ?? "";
|
|
39
|
-
return
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
// deletion starts and the member falls with it.
|
|
42
|
+
return groups.map((group, place) => {
|
|
43
|
+
const roots = group.filter((position) => {
|
|
44
|
+
// A dead member's dead container is on the member's own cycle, and the container
|
|
45
|
+
// is where the deletion starts.
|
|
44
46
|
const container = graph.parent[at[position] ?? 0] ?? OUTSIDE;
|
|
45
|
-
return
|
|
47
|
+
return referenced[componentOf[position] ?? 0] !== true && dead[container] !== true;
|
|
46
48
|
});
|
|
47
|
-
const fallen = falls[group] ?? [];
|
|
48
49
|
return {
|
|
49
|
-
id:
|
|
50
|
-
members:
|
|
50
|
+
id: componentId(place + 1),
|
|
51
|
+
members: group.map(idOf),
|
|
51
52
|
roots: roots.map(idOf),
|
|
52
|
-
|
|
53
|
-
deletableLines: linesSpanned(fallen.map((position) => graph.symbols[at[position] ?? 0])),
|
|
53
|
+
deletableLines: linesSpanned(group.map((position) => graph.symbols[at[position] ?? 0])),
|
|
54
54
|
};
|
|
55
55
|
});
|
|
56
56
|
}
|
|
@@ -127,10 +127,9 @@ function deadSubgraph(graph, dead, testOfDeadCode) {
|
|
|
127
127
|
return { at, adjacent };
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
|
-
* Each declaration's
|
|
130
|
+
* Each declaration's cycle and each cycle's members by site, by Tarjan's
|
|
131
131
|
* algorithm in one pass. The walk keeps its own stack of frames, so a chain of any length
|
|
132
|
-
* is walked without growing the call stack.
|
|
133
|
-
* each follows every component it reaches.
|
|
132
|
+
* is walked without growing the call stack.
|
|
134
133
|
*/
|
|
135
134
|
function connected(adjacent) {
|
|
136
135
|
const index = adjacent.map(() => UNVISITED);
|
|
@@ -193,97 +192,57 @@ function connected(adjacent) {
|
|
|
193
192
|
});
|
|
194
193
|
return { componentOf, members };
|
|
195
194
|
}
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
* the result is acyclic.
|
|
200
|
-
*/
|
|
201
|
-
function condense(adjacent, componentOf, count) {
|
|
202
|
-
const edges = Array.from({ length: count }, () => []);
|
|
203
|
-
const into = Array.from({ length: count }, () => 0);
|
|
204
|
-
const seen = new Set();
|
|
195
|
+
/** Per cycle, whether a dead declaration outside it references one of its members. */
|
|
196
|
+
function referencedCycles(adjacent, componentOf, count) {
|
|
197
|
+
const referenced = Array.from({ length: count }, () => false);
|
|
205
198
|
adjacent.forEach((targets, from) => {
|
|
206
199
|
for (const to of targets) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (a === b || seen.has(pair)) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
seen.add(pair);
|
|
214
|
-
edges[a]?.push(b);
|
|
215
|
-
into[b] = (into[b] ?? 0) + 1;
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
return { edges, into };
|
|
219
|
-
}
|
|
220
|
-
/** Per dead declaration, whether a dead component other than its own references it. */
|
|
221
|
-
function externallyReferenced(adjacent, componentOf) {
|
|
222
|
-
const referenced = adjacent.map(() => false);
|
|
223
|
-
adjacent.forEach((targets, from) => {
|
|
224
|
-
for (const to of targets) {
|
|
225
|
-
if (componentOf[from] !== componentOf[to]) {
|
|
226
|
-
referenced[to] = true;
|
|
200
|
+
const cycle = componentOf[to] ?? 0;
|
|
201
|
+
if (componentOf[from] !== cycle) {
|
|
202
|
+
referenced[cycle] = true;
|
|
227
203
|
}
|
|
228
204
|
}
|
|
229
205
|
});
|
|
230
206
|
return referenced;
|
|
231
207
|
}
|
|
232
208
|
/**
|
|
233
|
-
* The
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*/
|
|
237
|
-
function order(edges, members) {
|
|
238
|
-
const sequence = edges.map((_targets, component) => component).reverse();
|
|
239
|
-
const depth = edges.map(() => 0);
|
|
240
|
-
for (const from of sequence) {
|
|
241
|
-
for (const to of edges[from] ?? []) {
|
|
242
|
-
depth[to] = Math.max(depth[to] ?? 0, (depth[from] ?? 0) + 1);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return sequence.sort((a, b) => (depth[a] ?? 0) - (depth[b] ?? 0) || (members[a]?.[0] ?? 0) - (members[b]?.[0] ?? 0));
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Per component, the dead declarations one deletion of it removes. A root of the
|
|
249
|
-
* condensation carries its members and every component only it reaches; any other
|
|
250
|
-
* component carries its own members alone, because what it reaches is reached through a
|
|
251
|
-
* root above it as well.
|
|
209
|
+
* The weakly connected parts of the dead subgraph, each its positions in ascending
|
|
210
|
+
* order, ordered by their first position. Positions follow the graph's declaration
|
|
211
|
+
* order, so a part's first position is its first member's site.
|
|
252
212
|
*/
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
reach.set(root, reached);
|
|
276
|
-
for (const component of reached) {
|
|
277
|
-
owners[component] = (owners[component] ?? 0) + 1;
|
|
213
|
+
function joined(adjacent) {
|
|
214
|
+
const leader = adjacent.map((_edges, position) => position);
|
|
215
|
+
const find = (position) => {
|
|
216
|
+
let top = position;
|
|
217
|
+
while ((leader[top] ?? top) !== top) {
|
|
218
|
+
top = leader[top] ?? top;
|
|
219
|
+
}
|
|
220
|
+
// Every position on the walk points at the top afterwards, so a later find is short.
|
|
221
|
+
for (let step = position; step !== top;) {
|
|
222
|
+
const next = leader[step] ?? top;
|
|
223
|
+
leader[step] = top;
|
|
224
|
+
step = next;
|
|
225
|
+
}
|
|
226
|
+
return top;
|
|
227
|
+
};
|
|
228
|
+
adjacent.forEach((targets, from) => {
|
|
229
|
+
for (const to of targets) {
|
|
230
|
+
const a = find(from);
|
|
231
|
+
const b = find(to);
|
|
232
|
+
// The smaller position leads, so a part's leader is its first position.
|
|
233
|
+
leader[Math.max(a, b)] = Math.min(a, b);
|
|
278
234
|
}
|
|
279
235
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
236
|
+
const parts = new Map();
|
|
237
|
+
adjacent.forEach((_edges, position) => {
|
|
238
|
+
const top = find(position);
|
|
239
|
+
const part = parts.get(top);
|
|
240
|
+
if (part === undefined) {
|
|
241
|
+
parts.set(top, [position]);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
part.push(position);
|
|
286
245
|
}
|
|
287
|
-
return set.sort((a, b) => a - b);
|
|
288
246
|
});
|
|
247
|
+
return [...parts.values()];
|
|
289
248
|
}
|
package/dist/src/config.js
CHANGED
|
@@ -34,6 +34,12 @@ export function defaultConfig() {
|
|
|
34
34
|
maxFindings: 0,
|
|
35
35
|
failOn: "deny",
|
|
36
36
|
},
|
|
37
|
+
providers: {
|
|
38
|
+
analyzers: [
|
|
39
|
+
{ shape: "installed", name: "deadset-go", languages: ["go"], command: "deadset-go" },
|
|
40
|
+
{ shape: "installed", name: "deadset-ts", languages: ["ts"], command: "deadset-ts" },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
37
43
|
ts: {
|
|
38
44
|
testFiles: ["**/*.test.{ts,tsx,mts,cts}"],
|
|
39
45
|
entryFiles: [],
|