@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3
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 +95 -60
- package/dist/src/augmentations.js +14 -12
- package/dist/src/component-layer.js +1 -1
- package/dist/src/components.js +62 -20
- package/dist/src/configuration-files.js +230 -30
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +104 -13
- package/dist/src/convention-rows.js +10 -0
- package/dist/src/conventions.js +35 -1
- package/dist/src/dependencies.js +90 -32
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/element-access.js +341 -0
- package/dist/src/erased-stores.js +317 -0
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +78 -16
- package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
- package/dist/src/findings/emitters.js +1 -5
- package/dist/src/findings/self-check.js +27 -10
- package/dist/src/findings-pass.js +6 -5
- package/dist/src/in-operands.js +30 -0
- package/dist/src/indexed-access.js +264 -119
- package/dist/src/intra-function-parts.js +25 -11
- package/dist/src/inventory.js +28 -12
- package/dist/src/keyof-comparisons.js +170 -0
- package/dist/src/kinds.js +17 -7
- package/dist/src/manifest.js +6 -16
- package/dist/src/matrix.js +20 -2
- package/dist/src/query.js +3 -0
- package/dist/src/references.js +14 -3
- package/dist/src/report.js +4 -1
- package/dist/src/reporters/sarif.js +4 -0
- package/dist/src/roots.js +0 -1
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/serialization-contract.js +43 -14
- package/dist/src/session.js +12 -8
- package/dist/src/suppress-file.js +58 -6
- package/dist/src/suppress-inline.js +14 -5
- package/dist/src/suppress.js +64 -35
- package/dist/src/unimplemented-callees.js +312 -0
- package/dist/src/value-reach.js +7 -7
- package/dist/src/verbs/analyze.js +11 -9
- package/dist/src/workspace-programs.js +0 -3
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/analysis.ts +145 -82
- package/src/augmentations.ts +14 -11
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +104 -22
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +250 -31
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +135 -17
- package/src/convention-rows.ts +23 -0
- package/src/conventions.ts +53 -1
- package/src/dependencies.ts +119 -38
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/element-access.ts +446 -0
- package/src/entry-points.ts +1 -1
- package/src/erased-stores.ts +413 -0
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +3 -1
- package/src/finding.ts +3 -1
- package/src/findings/completion.ts +97 -21
- package/src/findings/dependencies-and-module-machinery.ts +1 -1
- package/src/findings/emitters.ts +4 -9
- package/src/findings/self-check.ts +31 -14
- package/src/findings-pass.ts +14 -7
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/in-operands.ts +45 -0
- package/src/indexed-access.ts +303 -137
- package/src/intra-function-parts.ts +41 -16
- package/src/inventory.ts +37 -33
- package/src/json-document.ts +1 -1
- package/src/keyof-comparisons.ts +211 -0
- package/src/kinds.ts +20 -7
- package/src/manifest.ts +7 -22
- package/src/matrix.ts +34 -2
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/query.ts +10 -0
- package/src/references.ts +30 -7
- package/src/report.ts +11 -5
- package/src/reporters/sarif.ts +4 -0
- package/src/roots.ts +0 -3
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/serialization-contract.ts +53 -14
- package/src/session.ts +34 -31
- package/src/stores.ts +1 -1
- package/src/suppress-file.ts +68 -5
- package/src/suppress-inline.ts +17 -7
- package/src/suppress.ts +79 -47
- package/src/template-field.ts +1 -1
- package/src/unimplemented-callees.ts +411 -0
- package/src/value-reach.ts +12 -8
- package/src/verbs/analyze.ts +15 -9
- package/src/verbs/verb.ts +1 -4
- package/src/workspace-programs.ts +2 -9
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
package/dist/src/analysis.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One run's passes, composed: the projects discovery names, opened in one snapshot
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* carries a project's identity in its type so a handle cannot cross to another
|
|
8
|
-
* project's checker, and that holds only for views the session itself hands out.
|
|
2
|
+
* One run's passes, composed: the projects discovery names, opened in one snapshot of one
|
|
3
|
+
* client, each read through the stages in dependency order, and the per-project answers
|
|
4
|
+
* merged into the run's. The stages take a project's view, which only this composition
|
|
5
|
+
* makes: a view carries a project's identity in its type so a handle cannot cross to
|
|
6
|
+
* another project's checker, which holds only for views the session hands out.
|
|
9
7
|
*/
|
|
10
8
|
import { lineOf, readComponent } from "./component-files.js";
|
|
11
9
|
import { consumerLoads, consumerReferences, moduleIdentity, } from "./consumers.js";
|
|
@@ -18,12 +16,13 @@ import { conventionFailuresFor, generatedFiles, readConventions, } from "./conve
|
|
|
18
16
|
import { readEdgeSides } from "./edges.js";
|
|
19
17
|
import { enumGroup } from "./enum-group.js";
|
|
20
18
|
import { generatedFile, generatedFilesOf } from "./generated-file.js";
|
|
19
|
+
import { inOperandNames } from "./in-operands.js";
|
|
21
20
|
import { sourceFilesOf } from "./source-files.js";
|
|
22
21
|
import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
|
|
23
22
|
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
|
|
24
23
|
import { globExpression } from "./glob.js";
|
|
25
24
|
import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
|
|
26
|
-
import {
|
|
25
|
+
import { unmatchedRootFindings } from "./findings/self-check.js";
|
|
27
26
|
import { frameworkLifecycle } from "./framework-lifecycle.js";
|
|
28
27
|
import { implementations, mergeImplementations } from "./implementations.js";
|
|
29
28
|
import { injectionContainer } from "./injection-container.js";
|
|
@@ -38,12 +37,12 @@ import { SetupError, setupLine } from "./setup-failure.js";
|
|
|
38
37
|
import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
|
|
39
38
|
import { reflectiveLookup } from "./reflective-lookup.js";
|
|
40
39
|
import { references, testFileRulesOf } from "./references.js";
|
|
41
|
-
import { roots, unmatchedEverywhere } from "./roots.js";
|
|
40
|
+
import { roots, unmatchedEverywhere, } from "./roots.js";
|
|
42
41
|
import { serializationContract } from "./serialization-contract.js";
|
|
43
42
|
import { outsideTestReferences, testFilesAt } from "./outside-tests.js";
|
|
44
43
|
import { joinedTestFiles, withJoinedTests } from "./test-joins.js";
|
|
45
44
|
import { supportReferences, testSupportFiles } from "./test-support.js";
|
|
46
|
-
import { diagnosticsOf, ownedAs, runSession } from "./session.js";
|
|
45
|
+
import { diagnosticsOf, ownedAs, runSession, } from "./session.js";
|
|
47
46
|
import { accessorsOf, storesOf } from "./stores.js";
|
|
48
47
|
import { isPartKind } from "./suppress.js";
|
|
49
48
|
import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
|
|
@@ -75,21 +74,47 @@ function compare(a, b) {
|
|
|
75
74
|
}
|
|
76
75
|
/**
|
|
77
76
|
* The manifests the analysis reads, each once: the target's own, then every workspace
|
|
78
|
-
* member's below the target root.
|
|
77
|
+
* member's and every nested package's below the target root.
|
|
79
78
|
*/
|
|
80
|
-
function conventionManifests(host, targetRoot, memberDirs) {
|
|
79
|
+
function conventionManifests(host, targetRoot, memberDirs, nestedDirs) {
|
|
81
80
|
const found = new Map();
|
|
82
81
|
if (host.kindOf(joinPath(targetRoot, "package.json")) === "file") {
|
|
83
82
|
found.set(targetRoot, { dir: targetRoot, path: "package.json" });
|
|
84
83
|
}
|
|
85
|
-
for (const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
for (const [dirs, nested] of [
|
|
85
|
+
[memberDirs, false],
|
|
86
|
+
[nestedDirs, true],
|
|
87
|
+
]) {
|
|
88
|
+
for (const dir of dirs) {
|
|
89
|
+
const below = relativePath(targetRoot, dir);
|
|
90
|
+
if (below !== undefined && !found.has(dir)) {
|
|
91
|
+
found.set(dir, { dir, path: `${below}/package.json`, nested });
|
|
92
|
+
}
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
return [...found.values()];
|
|
92
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The nested packages of a run: each directory below the target root, in no `node_modules`
|
|
99
|
+
* directory and none of `known`, that holds a `package.json` and lies at or above one of
|
|
100
|
+
* `files`, the program's source files and the compiler configuration files, in path order.
|
|
101
|
+
*/
|
|
102
|
+
function nestedPackageDirs(host, targetRoot, files, known) {
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
const found = [];
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
for (let dir = dirnamePath(file); dir !== targetRoot && !seen.has(dir) && relativePath(targetRoot, dir) !== undefined; dir = dirnamePath(dir)) {
|
|
107
|
+
seen.add(dir);
|
|
108
|
+
const below = relativePath(targetRoot, dir) ?? "";
|
|
109
|
+
if (!below.split("/").includes("node_modules") &&
|
|
110
|
+
!known.has(dir) &&
|
|
111
|
+
host.kindOf(joinPath(dir, "package.json")) === "file") {
|
|
112
|
+
found.push(dir);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return found.sort(compare);
|
|
117
|
+
}
|
|
93
118
|
/**
|
|
94
119
|
* Reads every project the scope discovers, and each declared consumer's, in one snapshot,
|
|
95
120
|
* each checked for errors first. `stage` is handed a target project and enumerates and roots
|
|
@@ -101,26 +126,41 @@ function conventionManifests(host, targetRoot, memberDirs) {
|
|
|
101
126
|
function readProjects(engine, host, scope, config, stage) {
|
|
102
127
|
const targetRoot = scope.target.path;
|
|
103
128
|
const discovered = discoverProjects(engine, host, scope, config.analysis.configurations);
|
|
129
|
+
if (discovered.configFiles.length === 0 && discovered.notBuilt.length === 0) {
|
|
130
|
+
throw new DiscoveryError(`${targetRoot} holds no compiler configuration, so the run holds no program`, []);
|
|
131
|
+
}
|
|
104
132
|
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
105
133
|
const loads = consumerLoads(engine, host, scope, discovered.configFiles);
|
|
106
134
|
const consumerOf = new Map(loads.flatMap((load) => load.configFiles.map((configFile) => [configFile, load.consumer])));
|
|
107
135
|
const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
|
|
108
136
|
const workspace = workspaceRun(engine, host, absoluteRoot, discovered.configFiles, readManifest(host, targetRoot));
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
const parse = (fileName, text) => engine.parseSourceFile(fileName, text);
|
|
138
|
+
const memberDirs = workspace?.memberDirs ?? [];
|
|
139
|
+
// The nested packages are known only once every program is open, and every visit reads
|
|
140
|
+
// the conventions and options they decide, so both are set in the session's first step.
|
|
141
|
+
let nestedDirs = [];
|
|
142
|
+
let conventions = readConventions(host, parse, [], config.ts.disabledConventions);
|
|
143
|
+
let options;
|
|
144
|
+
const readManifests = (opened) => {
|
|
145
|
+
nestedDirs = nestedPackageDirs(host, normalizePath(absoluteRoot), opened
|
|
146
|
+
.filter((one) => !consumerOf.has(one.configFile))
|
|
147
|
+
.flatMap((one) => [one.configFile, ...one.fileNames()]), new Set(memberDirs));
|
|
148
|
+
conventions = readConventions(host, parse, conventionManifests(host, absoluteRoot, memberDirs, nestedDirs), config.ts.disabledConventions);
|
|
149
|
+
options = {
|
|
150
|
+
host,
|
|
151
|
+
manifest: workspace?.manifest ?? readManifest(host, targetRoot),
|
|
152
|
+
otherPackages: [
|
|
153
|
+
...(workspace?.entries ?? []),
|
|
154
|
+
...packageEntries(host, normalizePath(absoluteRoot), nestedDirs),
|
|
155
|
+
],
|
|
156
|
+
patterns: config.rootPatterns,
|
|
157
|
+
entryFiles: config.ts.entryFiles,
|
|
158
|
+
conventions,
|
|
159
|
+
testFiles: config.ts.testFiles,
|
|
160
|
+
parse,
|
|
161
|
+
publishedAPI: config.targetKind === "library",
|
|
162
|
+
publishedMembers: new Set((workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir)),
|
|
163
|
+
};
|
|
124
164
|
};
|
|
125
165
|
const notBuilt = [...discovered.notBuilt];
|
|
126
166
|
const dropped = (configFile, error) => {
|
|
@@ -243,6 +283,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
243
283
|
.filter((file) => referenceOnly(file.fileName))
|
|
244
284
|
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
245
285
|
generated: generatedFilesOf(own, generatedFiles(conventions, own)),
|
|
286
|
+
testFileNames: conventions.testFiles,
|
|
246
287
|
};
|
|
247
288
|
};
|
|
248
289
|
for (const file of project.ownSourceFiles()) {
|
|
@@ -252,8 +293,8 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
252
293
|
}, discovered.derived ? "record" : "refuse", joins.size === 0
|
|
253
294
|
? workspace?.programs
|
|
254
295
|
: withJoinedTests(engine, host, workspace?.programs ?? new Map(), joins), () => {
|
|
255
|
-
outsideTests = testFilesAt(host, targetRoot, sourcePaths(host, targetRoot).filter((path) => !heldFiles.has(path) && testPatterns.some((pattern) => pattern.test(path))),
|
|
256
|
-
});
|
|
296
|
+
outsideTests = testFilesAt(host, targetRoot, sourcePaths(host, targetRoot).filter((path) => !heldFiles.has(path) && testPatterns.some((pattern) => pattern.test(path))), parse);
|
|
297
|
+
}, readManifests);
|
|
257
298
|
for (const configFile of session.unopened) {
|
|
258
299
|
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
259
300
|
throw new DiscoveryError(`${configFile} opened no project`, []);
|
|
@@ -285,7 +326,8 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
285
326
|
componentsUnread: workspace?.componentsUnread ?? [],
|
|
286
327
|
conventionsApplied: conventions.applied,
|
|
287
328
|
conventionUses: conventions.uses,
|
|
288
|
-
memberDirs
|
|
329
|
+
memberDirs,
|
|
330
|
+
nestedDirs,
|
|
289
331
|
outsideTests,
|
|
290
332
|
};
|
|
291
333
|
}
|
|
@@ -346,7 +388,7 @@ export function runRoots(engine, host, scope, config, provenance) {
|
|
|
346
388
|
};
|
|
347
389
|
}
|
|
348
390
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
349
|
-
const DETECTORS = new Map([
|
|
391
|
+
export const DETECTORS = new Map([
|
|
350
392
|
["interface-satisfaction", interfaceSatisfaction],
|
|
351
393
|
["enum-group", enumGroup],
|
|
352
394
|
["generated-file", generatedFile],
|
|
@@ -374,6 +416,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
374
416
|
const projectRead = opened.read();
|
|
375
417
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
376
418
|
testFiles: config.ts.testFiles,
|
|
419
|
+
testFileNames: projectRead.testFileNames,
|
|
377
420
|
});
|
|
378
421
|
const evidence = evidenceRecords({
|
|
379
422
|
project,
|
|
@@ -399,7 +442,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
399
442
|
line: lineOf(file.originalText, warning.offset),
|
|
400
443
|
reason: warning.reason,
|
|
401
444
|
}))),
|
|
402
|
-
extra: extra(project, projectRead, resolved.references),
|
|
445
|
+
extra: extra(project, projectRead, resolved.references, resolved.testFilePaths),
|
|
403
446
|
};
|
|
404
447
|
});
|
|
405
448
|
const outsidePaths = read.outsideTests.map((one) => one.path);
|
|
@@ -419,7 +462,6 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
419
462
|
const { projectRead } = one;
|
|
420
463
|
const satisfied = settled[index];
|
|
421
464
|
const detected = heldRecords(satisfied?.evidence ?? one.evidence, {
|
|
422
|
-
disabled,
|
|
423
465
|
mode,
|
|
424
466
|
testFiles: new Set([...one.testFiles, ...support]),
|
|
425
467
|
});
|
|
@@ -437,6 +479,9 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
437
479
|
...(projectRead.referenceOnly.length === 0
|
|
438
480
|
? {}
|
|
439
481
|
: { referenceOnly: projectRead.referenceOnly }),
|
|
482
|
+
...(projectRead.held.dependents.size === 0
|
|
483
|
+
? {}
|
|
484
|
+
: { dependents: projectRead.held.dependents }),
|
|
440
485
|
};
|
|
441
486
|
return {
|
|
442
487
|
configFile: projectRead.configFile,
|
|
@@ -484,6 +529,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
484
529
|
conventionsApplied: read.conventionsApplied,
|
|
485
530
|
conventionUses: read.conventionUses,
|
|
486
531
|
memberDirs: read.memberDirs,
|
|
532
|
+
nestedDirs: read.nestedDirs,
|
|
487
533
|
};
|
|
488
534
|
}
|
|
489
535
|
/**
|
|
@@ -610,28 +656,27 @@ function sweptOf(read, input) {
|
|
|
610
656
|
};
|
|
611
657
|
}
|
|
612
658
|
/**
|
|
613
|
-
* Sweeps the run the scope and the configuration describe. Each project's references
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
* configuration is a use. A project that carries an error ends the run before any
|
|
620
|
-
* project is swept.
|
|
659
|
+
* Sweeps the run the scope and the configuration describe. Each project's references and
|
|
660
|
+
* exemptions are read against its own inventory while its view is open; the projects then
|
|
661
|
+
* merge into one matrix in run order, swept under the caller's marks and mode with every
|
|
662
|
+
* project's exemptions. A record found in one configuration holds in every configuration
|
|
663
|
+
* that holds its declaration, because an exemption evidences a use the analysis cannot see.
|
|
664
|
+
* A project that carries an error ends the run before any project is swept.
|
|
621
665
|
*/
|
|
622
666
|
export function runSweep(engine, host, scope, config, input, detectors = DETECTORS) {
|
|
623
667
|
return sweptOf(readRun(engine, host, scope, config, input.mode, detectors, () => undefined), input);
|
|
624
668
|
}
|
|
625
669
|
/** Reads a run's projects with every per-project fact an emitter reads. */
|
|
626
|
-
function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
670
|
+
export function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
627
671
|
const targetRoot = scope.target.path;
|
|
628
|
-
const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
|
|
672
|
+
const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references, testFiles) => ({
|
|
629
673
|
deprecation: deprecatedDeclarations(project, read.held),
|
|
630
674
|
accessors: accessorsOf(project, read.held),
|
|
631
675
|
needs: projectNeeds(project, read.held, host, targetRoot, (fileName, text) => engine.parseSourceFile(fileName, text)),
|
|
632
676
|
implementations: implementations(project, read.held),
|
|
633
677
|
heldByInclusion: heldByInclusion(project, targetRoot),
|
|
634
678
|
generated: [...read.generated.keys()],
|
|
679
|
+
inOperands: inOperandNames(project, targetRoot, testFiles),
|
|
635
680
|
rooted: read.rooted,
|
|
636
681
|
directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
|
|
637
682
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
@@ -682,7 +727,7 @@ function nearestManifestDir(host, root, file) {
|
|
|
682
727
|
* The input every emitter reads over one sweep of a read run: the facts beside the sweep
|
|
683
728
|
* each kind family reads, every per-project one read in the same session as the sweep.
|
|
684
729
|
*/
|
|
685
|
-
function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
730
|
+
export function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
686
731
|
const targetRoot = scope.target.path;
|
|
687
732
|
const swept = sweptOf(read, input);
|
|
688
733
|
const { projects } = read;
|
|
@@ -695,12 +740,13 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
695
740
|
references: one.configured.references,
|
|
696
741
|
accessors: one.extra.accessors,
|
|
697
742
|
})), swept.exempt, input.mode),
|
|
698
|
-
dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id), members.read, members.unbuilt),
|
|
743
|
+
dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id), members.read, members.unbuilt, read.nestedDirs),
|
|
699
744
|
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
700
745
|
files: {
|
|
701
746
|
tree: sourceTree(host, targetRoot),
|
|
702
747
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
703
748
|
generated: new Set(projects.flatMap((one) => one.extra.generated)),
|
|
749
|
+
inOperands: new Set(projects.flatMap((one) => one.extra.inOperands)),
|
|
704
750
|
},
|
|
705
751
|
boundary: {
|
|
706
752
|
consumers: {
|
|
@@ -714,17 +760,6 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
714
760
|
intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
|
|
715
761
|
};
|
|
716
762
|
}
|
|
717
|
-
/**
|
|
718
|
-
* What every emitter reads of the run the scope and the configuration describe, swept
|
|
719
|
-
* under the caller's marks: the run's sweep, the facts beside it each kind family reads,
|
|
720
|
-
* and the sides of the target's declared cross-language edges that name a symbol of this
|
|
721
|
-
* language. The run reads no suppression document, so the self-check family has nothing
|
|
722
|
-
* to report.
|
|
723
|
-
*/
|
|
724
|
-
export function runEmitterInput(engine, host, scope, config, input, detectors = DETECTORS) {
|
|
725
|
-
const read = readEmitterRun(engine, host, scope, config, input.mode, detectors);
|
|
726
|
-
return emitterInputOver(host, scope, config, read, input, NO_SELF_CHECK);
|
|
727
|
-
}
|
|
728
763
|
/**
|
|
729
764
|
* Reads the run the scope and the configuration describe for its findings. The inline
|
|
730
765
|
* directives, the ignore file's entries and the baseline's rows are bound before any
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The references a use of an augmentation's declaration makes to what holds it.
|
|
3
|
-
*
|
|
4
|
-
* block
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The references a use of an augmentation's declaration makes to what holds it. A use of
|
|
3
|
+
* a declaration a `declare global` block or a module declaration holds is a use of the
|
|
4
|
+
* block, which no name spells or no import may name. An interface or namespace such a
|
|
5
|
+
* block, or the top level of a file that is no module, holds merges with the one of its
|
|
6
|
+
* name, which the language or a library types values by where the target never names it,
|
|
7
|
+
* as `import.meta` or `process.env`, so a use of its member is a use of it.
|
|
7
8
|
*/
|
|
8
9
|
/** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
|
|
9
10
|
function isGlobalBlock(symbol, held, files) {
|
|
@@ -13,24 +14,25 @@ function isGlobalBlock(symbol, held, files) {
|
|
|
13
14
|
!held.modules.has(symbol.id));
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
|
-
* For each reference to a declaration an augmentation block
|
|
17
|
-
*
|
|
18
|
-
* itself.
|
|
17
|
+
* For each reference to a declaration an augmentation block, or the global scope of a
|
|
18
|
+
* file in `scripts`, holds, a read at the same place of every interface and namespace
|
|
19
|
+
* between it and the block or the file, and of the block itself. `scripts` holds the
|
|
20
|
+
* files that are no module, by identifier.
|
|
19
21
|
*/
|
|
20
|
-
export function augmentationReferences(held, found) {
|
|
22
|
+
export function augmentationReferences(held, found, scripts = new Set()) {
|
|
21
23
|
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
22
24
|
const globals = new Set(held.symbols.filter((one) => isGlobalBlock(one, held, files)).map((one) => one.id));
|
|
23
|
-
if (globals.size === 0 && held.modules.size === 0) {
|
|
25
|
+
if (globals.size === 0 && held.modules.size === 0 && scripts.size === 0) {
|
|
24
26
|
return [];
|
|
25
27
|
}
|
|
26
28
|
const byId = new Map(held.symbols.map((one) => [one.id, one]));
|
|
27
29
|
const holders = (id) => {
|
|
28
30
|
const ids = [];
|
|
29
31
|
for (let at = byId.get(byId.get(id)?.parent ?? ""); at !== undefined; at = byId.get(at.parent)) {
|
|
30
|
-
if (globals.has(at.id)) {
|
|
32
|
+
if (globals.has(at.id) || held.modules.has(at.id)) {
|
|
31
33
|
return [...ids, at.id];
|
|
32
34
|
}
|
|
33
|
-
if (
|
|
35
|
+
if (scripts.has(at.id)) {
|
|
34
36
|
return ids;
|
|
35
37
|
}
|
|
36
38
|
if (at.kind === "interface" || at.kind === "namespace") {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { dirnamePath, joinPath } from "./paths.js";
|
|
9
9
|
/** The name the mapper's manifest declares and every configuration's entry names. */
|
|
10
|
-
|
|
10
|
+
const MAPPER_PACKAGE = "deadset-ts-component-files";
|
|
11
11
|
/** The manifest of the mapper's package, which runs `command`. */
|
|
12
12
|
export function mapperManifest(command) {
|
|
13
13
|
return `${JSON.stringify({
|
package/dist/src/components.js
CHANGED
|
@@ -26,34 +26,74 @@ export function listing(component, cascade) {
|
|
|
26
26
|
/**
|
|
27
27
|
* Groups the declarations `dead` marks into components, ordered by their first member's
|
|
28
28
|
* site; `dead`, `testOfDeadCode` and `unreferencedTest` hold one flag per declaration of
|
|
29
|
-
* the graph. Root members sit on the cycles no other cycle references. A
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* the graph. Root members sit on the cycles no other cycle references. A component is a
|
|
30
|
+
* weakly connected part of the dead subgraph, because a cycle two roots reach is dead only
|
|
31
|
+
* through both. The held declarations `positional` decides form components of their own,
|
|
32
|
+
* and a dead declaration that is not held shares one with each of its dead dependents.
|
|
32
33
|
*/
|
|
33
|
-
export function componentsOf(graph, dead, testOfDeadCode, unreferencedTest = []) {
|
|
34
|
-
const { at, adjacent } = deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest);
|
|
34
|
+
export function componentsOf(graph, dead, testOfDeadCode, unreferencedTest = [], positional = { dependents: [], live: [] }) {
|
|
35
|
+
const { at, position, adjacent, references } = deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest);
|
|
35
36
|
if (at.length === 0) {
|
|
36
37
|
return [];
|
|
37
38
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const dependents = at.map((index) => (positional.dependents[index] ?? []).map((to) => ({
|
|
40
|
+
to: position[to] ?? UNVISITED,
|
|
41
|
+
live: positional.live[to] === true,
|
|
42
|
+
})));
|
|
43
|
+
const held = heldSet(dependents, references);
|
|
44
|
+
const kept = adjacent.map((targets, from) => targets.filter((to) => held[to] === held[from]));
|
|
45
|
+
const { componentOf, members } = connected(kept);
|
|
46
|
+
const referenced = referencedCycles(kept, componentOf, members.length);
|
|
47
|
+
const groups = joined(kept.map((targets, from) => [
|
|
48
|
+
...targets,
|
|
49
|
+
...(held[from] === true
|
|
50
|
+
? []
|
|
51
|
+
: (dependents[from] ?? []).flatMap(({ to }) => to === UNVISITED || held[to] === true ? [] : [to])),
|
|
52
|
+
]));
|
|
53
|
+
const idOf = (place) => graph.symbols[at[place] ?? 0]?.id ?? "";
|
|
42
54
|
return groups.map((group, place) => {
|
|
43
|
-
const roots = group.filter((
|
|
55
|
+
const roots = group.filter((one) => {
|
|
44
56
|
// A dead member's dead container is on the member's own cycle, and the container
|
|
45
57
|
// is where the deletion starts.
|
|
46
|
-
const container = graph.parent[at[
|
|
47
|
-
return referenced[componentOf[
|
|
58
|
+
const container = graph.parent[at[one] ?? 0] ?? OUTSIDE;
|
|
59
|
+
return referenced[componentOf[one] ?? 0] !== true && dead[container] !== true;
|
|
48
60
|
});
|
|
61
|
+
const isHeld = group.every((one) => held[one] === true);
|
|
49
62
|
return {
|
|
50
63
|
id: componentId(place + 1),
|
|
51
64
|
members: group.map(idOf),
|
|
52
65
|
roots: roots.map(idOf),
|
|
53
|
-
deletableLines: linesSpanned(group.map((
|
|
66
|
+
deletableLines: isHeld ? 0 : linesSpanned(group.map((one) => graph.symbols[at[one] ?? 0])),
|
|
67
|
+
held: isHeld,
|
|
54
68
|
};
|
|
55
69
|
});
|
|
56
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Per place of the dead subgraph, whether the declaration is held: one with a live
|
|
73
|
+
* dependent, and each dead declaration a held one references or has as a dependent,
|
|
74
|
+
* until no further one joins.
|
|
75
|
+
*/
|
|
76
|
+
function heldSet(dependents, references) {
|
|
77
|
+
const held = dependents.map((after) => after.some((one) => one.live));
|
|
78
|
+
const before = dependents.map(() => []);
|
|
79
|
+
dependents.forEach((after, from) => {
|
|
80
|
+
for (const { to } of after) {
|
|
81
|
+
if (to !== UNVISITED) {
|
|
82
|
+
before[to]?.push(from);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const pending = held.flatMap((one, place) => (one ? [place] : []));
|
|
87
|
+
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
88
|
+
for (const reached of [...(references[next] ?? []), ...(before[next] ?? [])]) {
|
|
89
|
+
if (held[reached] !== true) {
|
|
90
|
+
held[reached] = true;
|
|
91
|
+
pending.push(reached);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return held;
|
|
96
|
+
}
|
|
57
97
|
/**
|
|
58
98
|
* The distinct source lines a set of declarations spans, each declaration at least the
|
|
59
99
|
* line it starts on. A member written inside its container's span adds no line, because
|
|
@@ -89,12 +129,12 @@ function linesSpanned(symbols) {
|
|
|
89
129
|
return total;
|
|
90
130
|
}
|
|
91
131
|
/**
|
|
92
|
-
* The dead declarations by
|
|
93
|
-
*
|
|
94
|
-
* each reference one makes to another, except an unreferenced test-file declaration's
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
132
|
+
* The dead declarations by subgraph place (`at`, and `position` per graph declaration),
|
|
133
|
+
* a test file's only where it tests dead code or nothing the tests reach. `adjacent` holds
|
|
134
|
+
* each reference one makes to another, except an unreferenced test-file declaration's to
|
|
135
|
+
* production code, which decides nothing of its target; an edge each way between a dead
|
|
136
|
+
* member and its dead container; and one back from each production target of an admitted
|
|
137
|
+
* test, which puts the test in its subject's component. `references` holds the first alone.
|
|
98
138
|
*/
|
|
99
139
|
function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
|
|
100
140
|
const at = [];
|
|
@@ -107,6 +147,7 @@ function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
|
|
|
107
147
|
return at.length - 1;
|
|
108
148
|
});
|
|
109
149
|
const adjacent = at.map(() => []);
|
|
150
|
+
const references = at.map(() => []);
|
|
110
151
|
at.forEach((index, from) => {
|
|
111
152
|
for (const edge of graph.out[index] ?? []) {
|
|
112
153
|
const to = position[edge.to] ?? UNVISITED;
|
|
@@ -114,6 +155,7 @@ function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
|
|
|
114
155
|
continue;
|
|
115
156
|
}
|
|
116
157
|
adjacent[from]?.push(to);
|
|
158
|
+
references[from]?.push(to);
|
|
117
159
|
if (testOfDeadCode[index] === true &&
|
|
118
160
|
graph.test[edge.to] !== true &&
|
|
119
161
|
graph.subject[edge.to] === true) {
|
|
@@ -126,7 +168,7 @@ function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
|
|
|
126
168
|
adjacent[container]?.push(from);
|
|
127
169
|
}
|
|
128
170
|
});
|
|
129
|
-
return { at, adjacent };
|
|
171
|
+
return { at, position, adjacent, references };
|
|
130
172
|
}
|
|
131
173
|
/**
|
|
132
174
|
* Each declaration's cycle and each cycle's members by site, by Tarjan's
|