@cplieger/deadset-ts 4.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bin/component-mapper.js +5 -0
- package/dist/bin/mapper-connection.js +101 -0
- package/dist/bin/node-host.js +24 -2
- package/dist/src/analysis.js +301 -41
- package/dist/src/component-files.js +612 -0
- package/dist/src/component-layer.js +132 -0
- package/dist/src/config.js +9 -2
- package/dist/src/configuration-files.js +290 -0
- package/dist/src/conformance-record.js +3 -9
- package/dist/src/contextual-uses.js +240 -0
- package/dist/src/convention-rows.js +389 -0
- package/dist/src/conventions.js +565 -0
- package/dist/src/dependencies.js +36 -3
- package/dist/src/destructuring.js +6 -6
- package/dist/src/discover.js +10 -3
- package/dist/src/emit-map.js +72 -0
- package/dist/src/entry-point-gaps.js +81 -35
- package/dist/src/entry-points.js +50 -32
- package/dist/src/exempt-classes.js +6 -1
- package/dist/src/exempt.js +32 -20
- package/dist/src/findings/completion.js +46 -32
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/interfaces.js +6 -2
- package/dist/src/findings/reads-and-writes.js +53 -9
- package/dist/src/findings/unused-declarations.js +47 -3
- package/dist/src/generated-file.js +24 -0
- package/dist/src/graph.js +13 -5
- package/dist/src/interface-satisfaction.js +2 -68
- package/dist/src/inventory.js +8 -3
- package/dist/src/kinds.js +6 -5
- package/dist/src/manifest.js +37 -11
- package/dist/src/matrix.js +21 -13
- package/dist/src/partial.js +3 -1
- package/dist/src/pnpm-workspace.js +120 -0
- package/dist/src/position.js +17 -2
- package/dist/src/print.js +2 -0
- package/dist/src/query.js +15 -0
- package/dist/src/references.js +404 -36
- package/dist/src/report.js +26 -6
- package/dist/src/reporters/annotations.js +10 -3
- package/dist/src/reporters/template.js +3 -0
- package/dist/src/resolve.js +37 -11
- package/dist/src/roots.js +37 -8
- package/dist/src/run.js +13 -3
- package/dist/src/schema.js +2 -0
- package/dist/src/session.js +141 -34
- package/dist/src/setup-failure.js +23 -0
- package/dist/src/source-files.js +4 -61
- package/dist/src/stores.js +3 -0
- package/dist/src/sweep.js +4 -2
- package/dist/src/template-field.js +38 -6
- package/dist/src/test-support.js +59 -0
- package/dist/src/type-errors.js +252 -0
- package/dist/src/type-query-alias.js +92 -0
- package/dist/src/value-flow.js +74 -0
- package/dist/src/verbs/analyze.js +24 -4
- package/dist/src/verbs/explain.js +4 -1
- package/dist/src/verbs/print-projects.js +17 -56
- package/dist/src/verbs/print-retained.js +7 -2
- package/dist/src/verbs/print-roots.js +7 -2
- package/dist/src/version-range.js +255 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-manifests.js +68 -0
- package/dist/src/workspace-programs.js +342 -0
- package/dist/src/workspace-resolution.js +307 -0
- package/dist/src/workspace-run.js +75 -0
- package/dist/src/workspace.js +358 -0
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/analysis.ts +475 -72
- package/src/component-files.ts +718 -0
- package/src/component-layer.ts +160 -0
- package/src/config.ts +11 -2
- package/src/configuration-files.ts +370 -0
- package/src/conformance-record.ts +3 -10
- package/src/contextual-uses.ts +354 -0
- package/src/convention-rows.ts +448 -0
- package/src/conventions.ts +788 -0
- package/src/dependencies.ts +49 -2
- package/src/destructuring.ts +16 -6
- package/src/discover.ts +10 -6
- package/src/emit-map.ts +93 -0
- package/src/entry-point-gaps.ts +92 -38
- package/src/entry-points.ts +79 -36
- package/src/exempt-classes.ts +6 -1
- package/src/exempt.ts +69 -20
- package/src/file-facts.ts +2 -0
- package/src/findings/completion.ts +55 -36
- package/src/findings/emitters.ts +32 -1
- package/src/findings/interfaces.ts +9 -2
- package/src/findings/reads-and-writes.ts +70 -9
- package/src/findings/unused-declarations.ts +60 -3
- package/src/generated-file.ts +27 -0
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/interface-satisfaction.ts +1 -92
- package/src/inventory.ts +17 -3
- package/src/kinds.ts +7 -5
- package/src/manifest.ts +47 -14
- package/src/matrix.ts +43 -15
- package/src/partial.ts +6 -1
- package/src/pnpm-workspace.ts +135 -0
- package/src/position.ts +23 -2
- package/src/print.ts +2 -0
- package/src/query.ts +26 -0
- package/src/references.ts +472 -40
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +3 -0
- package/src/resolve.ts +65 -11
- package/src/roots.ts +70 -9
- package/src/run.ts +15 -5
- package/src/schema.ts +2 -0
- package/src/session.ts +218 -37
- package/src/setup-failure.ts +39 -0
- package/src/source-files.ts +4 -66
- package/src/stores.ts +9 -0
- package/src/sweep.ts +9 -2
- package/src/template-field.ts +42 -7
- package/src/test-support.ts +77 -0
- package/src/type-errors.ts +340 -0
- package/src/type-query-alias.ts +133 -0
- package/src/value-flow.ts +104 -0
- package/src/verbs/analyze.ts +38 -4
- package/src/verbs/explain.ts +4 -1
- package/src/verbs/print-projects.ts +17 -69
- package/src/verbs/print-retained.ts +7 -2
- package/src/verbs/print-roots.ts +7 -2
- package/src/verbs/verb.ts +6 -3
- package/src/version-range.ts +309 -0
- package/src/version.ts +1 -1
- package/src/workspace-manifests.ts +100 -0
- package/src/workspace-programs.ts +486 -0
- package/src/workspace-resolution.ts +393 -0
- package/src/workspace-run.ts +148 -0
- package/src/workspace.ts +463 -0
package/dist/src/analysis.js
CHANGED
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
* carries a project's identity in its type so a handle cannot cross to another
|
|
8
8
|
* project's checker, and that holds only for views the session itself hands out.
|
|
9
9
|
*/
|
|
10
|
+
import { lineOf, readComponent } from "./component-files.js";
|
|
10
11
|
import { consumerLoads, consumerReferences, moduleIdentity, } from "./consumers.js";
|
|
11
12
|
import { configuredDeclarations, configuredRef, declarationsNamed, } from "./configured-declarations.js";
|
|
12
13
|
import { decorator } from "./decorator.js";
|
|
13
14
|
import { dependenciesOf, projectNeeds } from "./dependencies.js";
|
|
14
15
|
import { deprecatedDeclarations } from "./deprecation.js";
|
|
15
16
|
import { discoverProjects, DiscoveryError, isGuess, projectErrors, renderDiagnostic, targetRelative, } from "./discover.js";
|
|
17
|
+
import { conventionFailuresFor, generatedFiles, readConventions, } from "./conventions.js";
|
|
16
18
|
import { readEdgeSides } from "./edges.js";
|
|
17
19
|
import { enumGroup } from "./enum-group.js";
|
|
18
|
-
import {
|
|
20
|
+
import { generatedFile } from "./generated-file.js";
|
|
21
|
+
import { sourceFilesOf } from "./source-files.js";
|
|
22
|
+
import { detectExemptions, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
|
|
19
23
|
import { heldByInclusion, sourceTree } from "./file-facts.js";
|
|
20
24
|
import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
|
|
21
25
|
import { NO_SELF_CHECK, unmatchedRootFindings, } from "./findings/self-check.js";
|
|
@@ -26,19 +30,23 @@ import { interfaceSatisfaction } from "./interface-satisfaction.js";
|
|
|
26
30
|
import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
|
|
27
31
|
import { inventory } from "./inventory.js";
|
|
28
32
|
import { readManifest } from "./manifest.js";
|
|
29
|
-
import { matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
|
|
30
|
-
import { relativePath, resolvePath } from "./paths.js";
|
|
31
|
-
import { byPosition } from "./position.js";
|
|
33
|
+
import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
|
|
34
|
+
import { joinPath, relativePath, resolvePath } from "./paths.js";
|
|
35
|
+
import { byPosition, isComponentFile } from "./position.js";
|
|
36
|
+
import { SetupError, setupLine } from "./setup-failure.js";
|
|
37
|
+
import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
|
|
32
38
|
import { reflectiveLookup } from "./reflective-lookup.js";
|
|
33
39
|
import { references, testFileRulesOf } from "./references.js";
|
|
34
40
|
import { roots, unmatchedEverywhere } from "./roots.js";
|
|
35
41
|
import { serializationContract } from "./serialization-contract.js";
|
|
36
|
-
import {
|
|
42
|
+
import { supportReferences, testSupportFiles } from "./test-support.js";
|
|
43
|
+
import { diagnosticsOf, ownedAs, runSession } from "./session.js";
|
|
37
44
|
import { accessorsOf, storesOf } from "./stores.js";
|
|
38
45
|
import { isPartKind } from "./suppress.js";
|
|
39
46
|
import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
|
|
40
47
|
import { inlineDirectives, inlineSuppressions } from "./suppress-inline.js";
|
|
41
48
|
import { readTemplates, templateField } from "./template-field.js";
|
|
49
|
+
import { ownErrors, workspaceRun } from "./workspace-run.js";
|
|
42
50
|
/** The setting whose source decides where a finding about a configured root sits. */
|
|
43
51
|
const ROOTS_SETTING = "roots.patterns";
|
|
44
52
|
/**
|
|
@@ -63,12 +71,29 @@ function compare(a, b) {
|
|
|
63
71
|
return a < b ? -1 : 1;
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
* The manifests the analysis reads, each once: the target's own, then every workspace
|
|
75
|
+
* member's below the target root.
|
|
76
|
+
*/
|
|
77
|
+
function conventionManifests(host, targetRoot, memberDirs) {
|
|
78
|
+
const found = new Map();
|
|
79
|
+
if (host.kindOf(joinPath(targetRoot, "package.json")) === "file") {
|
|
80
|
+
found.set(targetRoot, { dir: targetRoot, path: "package.json" });
|
|
81
|
+
}
|
|
82
|
+
for (const dir of memberDirs) {
|
|
83
|
+
const below = relativePath(targetRoot, dir);
|
|
84
|
+
if (below !== undefined && !found.has(dir)) {
|
|
85
|
+
found.set(dir, { dir, path: `${below}/package.json` });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [...found.values()];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reads every project the scope discovers, and each declared consumer's, in one snapshot,
|
|
92
|
+
* each checked for errors first. `stage` is handed a target project and enumerates and roots
|
|
93
|
+
* it if it reads it; a consumer project is read for its references to the target. A type error
|
|
94
|
+
* in an own file skips the unit holding it. A guess that does not load or meets a setup failure
|
|
95
|
+
* is dropped into `notBuilt` (see {@link isGuess}); any other such project fails the run, as
|
|
96
|
+
* does a run whose every guess was dropped.
|
|
72
97
|
*/
|
|
73
98
|
function readProjects(engine, host, scope, config, stage) {
|
|
74
99
|
const targetRoot = scope.target.path;
|
|
@@ -76,14 +101,20 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
76
101
|
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
77
102
|
const loads = consumerLoads(engine, host, scope, discovered.configFiles);
|
|
78
103
|
const consumerOf = new Map(loads.flatMap((load) => load.configFiles.map((configFile) => [configFile, load.consumer])));
|
|
104
|
+
const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
|
|
105
|
+
const workspace = workspaceRun(engine, host, absoluteRoot, discovered.configFiles, readManifest(host, targetRoot));
|
|
106
|
+
const conventions = readConventions(host, (fileName, text) => engine.parseSourceFile(fileName, text), conventionManifests(host, absoluteRoot, workspace?.memberDirs ?? []), config.ts.disabledConventions);
|
|
79
107
|
const options = {
|
|
80
|
-
|
|
108
|
+
host,
|
|
109
|
+
manifest: workspace?.manifest ?? readManifest(host, targetRoot),
|
|
110
|
+
workspaceEntries: workspace?.entries,
|
|
81
111
|
patterns: config.rootPatterns,
|
|
82
112
|
entryFiles: config.ts.entryFiles,
|
|
113
|
+
conventions,
|
|
83
114
|
testFiles: config.ts.testFiles,
|
|
115
|
+
parse: (fileName, text) => engine.parseSourceFile(fileName, text),
|
|
84
116
|
publishedAPI: config.targetKind === "library",
|
|
85
117
|
};
|
|
86
|
-
const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
|
|
87
118
|
const notBuilt = [...discovered.notBuilt];
|
|
88
119
|
const dropped = (configFile, error) => {
|
|
89
120
|
notBuilt.push({
|
|
@@ -93,10 +124,36 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
93
124
|
});
|
|
94
125
|
};
|
|
95
126
|
const failures = [];
|
|
127
|
+
const unbuildable = [];
|
|
128
|
+
const setupDropped = [];
|
|
96
129
|
const consumed = new Map();
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
130
|
+
const setupMet = (configFile, met) => {
|
|
131
|
+
const [first] = met;
|
|
132
|
+
if (first !== undefined && isGuess(discovered, configFile)) {
|
|
133
|
+
dropped(configFile, setupLine(first));
|
|
134
|
+
setupDropped.push(...met);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
unbuildable.push(...met);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (opened) => {
|
|
141
|
+
const consumer = consumerOf.get(opened.configFile);
|
|
142
|
+
const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
|
|
143
|
+
if (setup !== undefined) {
|
|
144
|
+
setupMet(opened.configFile, [
|
|
145
|
+
{ setupClass: "workspace-member-without-source", detail: setup },
|
|
146
|
+
]);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const referenceOnly = workspace === undefined || consumer !== undefined
|
|
150
|
+
? () => false
|
|
151
|
+
: workspace.referenceOnly(opened.configFile, opened.rootFiles);
|
|
152
|
+
const project = workspace === undefined || consumer !== undefined
|
|
153
|
+
? opened
|
|
154
|
+
: ownedAs(opened, (file, fromExternalLibrary) => workspace.ownFile(file, fromExternalLibrary));
|
|
155
|
+
const raw = projectErrors(diagnosticsOf(project));
|
|
156
|
+
const errors = { load: raw.load, check: ownErrors(raw.check, referenceOnly) };
|
|
100
157
|
const [unloaded] = errors.load;
|
|
101
158
|
if (unloaded !== undefined &&
|
|
102
159
|
consumer === undefined &&
|
|
@@ -104,8 +161,37 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
104
161
|
dropped(project.configFile, renderDiagnostic(unloaded, (path) => relativePath(absoluteRoot, path) ?? path));
|
|
105
162
|
return undefined;
|
|
106
163
|
}
|
|
107
|
-
if (errors.load.length > 0
|
|
108
|
-
failures.push(...errors.load
|
|
164
|
+
if (errors.load.length > 0) {
|
|
165
|
+
failures.push(...errors.load);
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
const reading = readErrors(project, errors.check, {
|
|
169
|
+
host,
|
|
170
|
+
targetRoot: consumer === undefined
|
|
171
|
+
? absoluteRoot
|
|
172
|
+
: resolvePath(host.workingDirectory(), consumer.path),
|
|
173
|
+
paths: pathsOf(engine, project.configFile),
|
|
174
|
+
});
|
|
175
|
+
if (reading.failing.length > 0) {
|
|
176
|
+
failures.push(...reading.failing);
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
if (consumer !== undefined && reading.missing.length > 0) {
|
|
180
|
+
unbuildable.push(...reading.missing.map((one) => ({
|
|
181
|
+
setupClass: "missing-consumer",
|
|
182
|
+
detail: `the consumer ${consumer.path} does not build, its dependencies not installed: ${one.detail}`,
|
|
183
|
+
})));
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
if (reading.missing.length > 0) {
|
|
187
|
+
setupMet(project.configFile, reading.missing);
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const unconventional = consumer === undefined
|
|
191
|
+
? conventionFailuresFor(conventions, project.configFile, project.ownSourceFiles().map((file) => file.fileName))
|
|
192
|
+
: [];
|
|
193
|
+
if (unconventional.length > 0) {
|
|
194
|
+
setupMet(project.configFile, unconventional);
|
|
109
195
|
return undefined;
|
|
110
196
|
}
|
|
111
197
|
if (consumer !== undefined) {
|
|
@@ -117,22 +203,53 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
117
203
|
}
|
|
118
204
|
return undefined;
|
|
119
205
|
}
|
|
120
|
-
const held = inventory(project, host, targetRoot);
|
|
121
|
-
const rooted = roots(project, held, targetRoot, options);
|
|
122
206
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
123
|
-
|
|
124
|
-
|
|
207
|
+
const read = () => {
|
|
208
|
+
const held = inventory(project, host, targetRoot);
|
|
209
|
+
const found = roots(project, held, targetRoot, options);
|
|
210
|
+
const kept = skippedRoots(project, held, reading.skips);
|
|
211
|
+
const rooted = kept.length === 0
|
|
212
|
+
? found
|
|
213
|
+
: {
|
|
214
|
+
...found,
|
|
215
|
+
liveUnderReachability: [
|
|
216
|
+
...found.liveUnderReachability,
|
|
217
|
+
...kept.map((id) => ({ id, kind: "type-error", source: "" })),
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
return {
|
|
221
|
+
configuration,
|
|
222
|
+
configFile: project.configFile,
|
|
223
|
+
held,
|
|
224
|
+
rooted,
|
|
225
|
+
typeErrorSkips: reading.skips.map((one) => one.record),
|
|
226
|
+
skipped: skippedUnits(reading.skips, absoluteRoot),
|
|
227
|
+
referenceOnly: project
|
|
228
|
+
.ownSourceFiles()
|
|
229
|
+
.filter((file) => referenceOnly(file.fileName))
|
|
230
|
+
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
231
|
+
generated: generatedFiles(conventions, sourceFilesOf(project, targetRoot)),
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
return stage(project, { configuration, read });
|
|
235
|
+
}, discovered.derived ? "record" : "refuse", workspace?.programs);
|
|
125
236
|
for (const configFile of session.unopened) {
|
|
126
237
|
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
127
238
|
throw new DiscoveryError(`${configFile} opened no project`, []);
|
|
128
239
|
}
|
|
129
240
|
dropped(configFile, "the compiler opened no project for the configuration");
|
|
130
241
|
}
|
|
242
|
+
if (unbuildable.length > 0) {
|
|
243
|
+
throw new SetupError(unbuildable);
|
|
244
|
+
}
|
|
131
245
|
if (failures.length > 0) {
|
|
132
246
|
throw new DiscoveryError(`${String(failures.length)} error(s), so no answer was produced`, failures);
|
|
133
247
|
}
|
|
134
248
|
const answers = session.projects.filter((answer) => answer !== undefined);
|
|
135
249
|
const [first] = notBuilt;
|
|
250
|
+
if (answers.length === 0 && setupDropped.length > 0) {
|
|
251
|
+
throw new SetupError(setupDropped);
|
|
252
|
+
}
|
|
136
253
|
if (answers.length === 0 && first !== undefined) {
|
|
137
254
|
throw new DiscoveryError(`no configuration discovery derived could be built; ${first.id}: ${first.error}`, []);
|
|
138
255
|
}
|
|
@@ -144,8 +261,19 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
144
261
|
},
|
|
145
262
|
notBuilt,
|
|
146
263
|
unanswered: session.unanswered,
|
|
264
|
+
componentsUnread: workspace?.componentsUnread ?? [],
|
|
265
|
+
conventionsApplied: conventions.applied,
|
|
147
266
|
};
|
|
148
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* The projects the run the scope and the configuration describe analyzes, each read
|
|
270
|
+
* for the errors and setup failures that decide whether it is analyzed, dropped or
|
|
271
|
+
* fails the run, exactly as the analysis reads them.
|
|
272
|
+
*/
|
|
273
|
+
export function runProjects(engine, host, scope, config) {
|
|
274
|
+
const read = readProjects(engine, host, scope, config, (_project, opened) => opened.configuration);
|
|
275
|
+
return { configurations: read.answers, notBuilt: read.notBuilt, unanswered: read.unanswered };
|
|
276
|
+
}
|
|
149
277
|
/**
|
|
150
278
|
* The root set of the run the scope and the configuration describe.
|
|
151
279
|
*
|
|
@@ -154,7 +282,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
154
282
|
*/
|
|
155
283
|
export function runRoots(engine, host, scope, config, provenance) {
|
|
156
284
|
const targetRoot = scope.target.path;
|
|
157
|
-
const read = readProjects(engine, host, scope, config, (_project,
|
|
285
|
+
const read = readProjects(engine, host, scope, config, (_project, opened) => opened.read());
|
|
158
286
|
const projects = read.answers;
|
|
159
287
|
const configurations = [];
|
|
160
288
|
const merged = new Map();
|
|
@@ -197,6 +325,7 @@ export function runRoots(engine, host, scope, config, provenance) {
|
|
|
197
325
|
const DETECTORS = new Map([
|
|
198
326
|
["interface-satisfaction", interfaceSatisfaction],
|
|
199
327
|
["enum-group", enumGroup],
|
|
328
|
+
["generated-file", generatedFile],
|
|
200
329
|
["template-field", templateField],
|
|
201
330
|
["reflective-lookup", reflectiveLookup],
|
|
202
331
|
["decorator", decorator],
|
|
@@ -217,36 +346,64 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
217
346
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
218
347
|
: readTemplates(host, targetRoot, config.analysis);
|
|
219
348
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
220
|
-
const read = readProjects(engine, host, scope, config, (project,
|
|
349
|
+
const read = readProjects(engine, host, scope, config, (project, opened) => {
|
|
350
|
+
const projectRead = opened.read();
|
|
221
351
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
222
352
|
testFiles: config.ts.testFiles,
|
|
223
353
|
});
|
|
354
|
+
const support = testSupportFiles(projectRead.held.symbols, resolved.references, projectRead.rooted.liveUnderReachability, resolved.testFilePaths);
|
|
355
|
+
const detected = detectExemptions({
|
|
356
|
+
project,
|
|
357
|
+
held: projectRead.held,
|
|
358
|
+
targetRoot,
|
|
359
|
+
templates,
|
|
360
|
+
ts: config.ts,
|
|
361
|
+
consumers,
|
|
362
|
+
...(config.analysis.generatedFiles === "include"
|
|
363
|
+
? {}
|
|
364
|
+
: { generated: projectRead.generated }),
|
|
365
|
+
}, detectors, {
|
|
366
|
+
disabled,
|
|
367
|
+
mode,
|
|
368
|
+
testFiles: new Set([...resolved.testFilePaths, ...support]),
|
|
369
|
+
});
|
|
370
|
+
const exempt = detected.records;
|
|
371
|
+
const made = supportReferences(resolved.references, support);
|
|
224
372
|
const configured = {
|
|
225
373
|
configuration: projectRead.configuration,
|
|
226
374
|
symbols: projectRead.held.symbols,
|
|
227
|
-
references:
|
|
375
|
+
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
228
376
|
roots: projectRead.rooted.liveUnderReachability,
|
|
229
377
|
testFiles: resolved.testFilePaths,
|
|
378
|
+
...(support.size === 0 ? {} : { supportFiles: [...support].sort() }),
|
|
379
|
+
...(projectRead.referenceOnly.length === 0
|
|
380
|
+
? {}
|
|
381
|
+
: { referenceOnly: projectRead.referenceOnly }),
|
|
230
382
|
};
|
|
231
|
-
const exempt = computeExemptions({ project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers }, detectors, {
|
|
232
|
-
disabled,
|
|
233
|
-
mode,
|
|
234
|
-
testFiles: new Set(resolved.testFilePaths),
|
|
235
|
-
});
|
|
236
383
|
const unanswered = [
|
|
237
384
|
...projectRead.held.unanswered,
|
|
238
385
|
...projectRead.rooted.unanswered,
|
|
239
|
-
...
|
|
386
|
+
...made
|
|
240
387
|
.filter((reference) => reference.resolution === "by-name")
|
|
241
388
|
.map((reference) => reference.to),
|
|
242
389
|
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
243
390
|
];
|
|
391
|
+
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
392
|
+
const pathOf = (file) => relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
244
393
|
return {
|
|
245
394
|
configFile: projectRead.configFile,
|
|
246
395
|
configured,
|
|
247
396
|
exempt,
|
|
248
397
|
unanswered,
|
|
249
|
-
|
|
398
|
+
typeErrorSkips: projectRead.typeErrorSkips,
|
|
399
|
+
skipped: projectRead.skipped,
|
|
400
|
+
componentFiles: components.map(pathOf),
|
|
401
|
+
componentWarnings: components.flatMap((file) => readComponent(file.originalText).warnings.map((warning) => ({
|
|
402
|
+
path: pathOf(file),
|
|
403
|
+
line: lineOf(file.originalText, warning.offset),
|
|
404
|
+
reason: warning.reason,
|
|
405
|
+
}))),
|
|
406
|
+
extra: extra(project, projectRead, made),
|
|
250
407
|
};
|
|
251
408
|
});
|
|
252
409
|
const consumed = read.consumers.references;
|
|
@@ -272,8 +429,82 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
272
429
|
consumers: read.consumers.loaded,
|
|
273
430
|
notBuilt: read.notBuilt,
|
|
274
431
|
unanswered: read.unanswered,
|
|
432
|
+
componentsUnread: read.componentsUnread,
|
|
433
|
+
conventionsApplied: read.conventionsApplied,
|
|
275
434
|
};
|
|
276
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* The run swept under one set of marks with the records that hold. A record a component
|
|
438
|
+
* file's markup holds counts only while the file is live, and what it holds may be what
|
|
439
|
+
* keeps the file live, so each file's markup joins only once a sweep without it finds the
|
|
440
|
+
* file live, until a sweep finds no further file live.
|
|
441
|
+
*/
|
|
442
|
+
function holdingSweep(read, input) {
|
|
443
|
+
const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
|
|
444
|
+
let live = new Set();
|
|
445
|
+
for (;;) {
|
|
446
|
+
const swept = {
|
|
447
|
+
...input,
|
|
448
|
+
exempt: holdingWhile(read.exempt, live),
|
|
449
|
+
unanswered: read.unansweredHeld,
|
|
450
|
+
};
|
|
451
|
+
const result = sweepMatrix(read.matrix, swept);
|
|
452
|
+
const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
|
|
453
|
+
if (next.size === live.size) {
|
|
454
|
+
return { swept, result };
|
|
455
|
+
}
|
|
456
|
+
live = next;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The test references exemption evidence written in a test file makes: each is a read of
|
|
461
|
+
* the declaration it would retain, made by the test file at the evidence's site.
|
|
462
|
+
*/
|
|
463
|
+
function evidenceReferences(symbols, inTestFiles) {
|
|
464
|
+
if (inTestFiles.length === 0) {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
const files = new Map(symbols
|
|
468
|
+
.filter((symbol) => symbol.kind === "file")
|
|
469
|
+
.map((symbol) => [symbol.position.path, symbol.id]));
|
|
470
|
+
return inTestFiles.flatMap((record) => {
|
|
471
|
+
const from = files.get(record.site.path);
|
|
472
|
+
return from === undefined
|
|
473
|
+
? []
|
|
474
|
+
: [
|
|
475
|
+
{
|
|
476
|
+
from,
|
|
477
|
+
to: record.id,
|
|
478
|
+
position: record.site,
|
|
479
|
+
use: "read",
|
|
480
|
+
resolution: "syntax",
|
|
481
|
+
test: true,
|
|
482
|
+
},
|
|
483
|
+
];
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* The declarations every reference the mode counts stores into, a loaded consumer's
|
|
488
|
+
* reference counting as a read: what the write-only kind would report but for a record.
|
|
489
|
+
*/
|
|
490
|
+
function writtenOnly(read, mode) {
|
|
491
|
+
const written = new Set();
|
|
492
|
+
const readSome = new Set();
|
|
493
|
+
for (const { configured } of read.projects) {
|
|
494
|
+
for (const reference of configured.references) {
|
|
495
|
+
if (mode.production && reference.test) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (reference.use === "write" && reference.consumer === undefined) {
|
|
499
|
+
written.add(reference.to);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
readSome.add(reference.to);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return new Set([...written].filter((id) => !readSome.has(id)));
|
|
507
|
+
}
|
|
277
508
|
/**
|
|
278
509
|
* The sweep of a run read once, under one set of marks. Where a question went unanswered,
|
|
279
510
|
* the run is swept once more without what that question could have kept live and without
|
|
@@ -282,8 +513,8 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
282
513
|
* every declaration that makes such a reference.
|
|
283
514
|
*/
|
|
284
515
|
function sweptOf(read, input) {
|
|
285
|
-
const
|
|
286
|
-
const
|
|
516
|
+
const { swept, result } = holdingSweep(read, input);
|
|
517
|
+
const exempt = swept.exempt ?? [];
|
|
287
518
|
const held = new Set(read.unansweredHeld);
|
|
288
519
|
if (held.size > 0) {
|
|
289
520
|
// What a declaration reads where the checker did not answer is unknown, so a rule
|
|
@@ -302,7 +533,7 @@ function sweptOf(read, input) {
|
|
|
302
533
|
})));
|
|
303
534
|
const bare = {
|
|
304
535
|
...swept,
|
|
305
|
-
exempt:
|
|
536
|
+
exempt: exempt.filter((record) => !isUnansweredRecord(record)),
|
|
306
537
|
unanswered: [],
|
|
307
538
|
};
|
|
308
539
|
for (const candidate of sweepMatrix(answered, bare).candidates) {
|
|
@@ -316,10 +547,13 @@ function sweptOf(read, input) {
|
|
|
316
547
|
return {
|
|
317
548
|
matrix: read.matrix,
|
|
318
549
|
sweep: result,
|
|
319
|
-
|
|
550
|
+
exempt,
|
|
551
|
+
retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
|
|
320
552
|
notBuilt: read.notBuilt,
|
|
321
553
|
unanswered: read.unanswered,
|
|
322
554
|
heldByUnanswered: [...held],
|
|
555
|
+
skipped: read.projects.flatMap((one) => one.skipped),
|
|
556
|
+
componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(compare),
|
|
323
557
|
};
|
|
324
558
|
}
|
|
325
559
|
/**
|
|
@@ -341,11 +575,12 @@ function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
|
341
575
|
const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
|
|
342
576
|
deprecation: deprecatedDeclarations(project, read.held),
|
|
343
577
|
accessors: accessorsOf(project, read.held),
|
|
344
|
-
needs: projectNeeds(project, read.held, host),
|
|
578
|
+
needs: projectNeeds(project, read.held, host, targetRoot, (fileName, text) => engine.parseSourceFile(fileName, text)),
|
|
345
579
|
implementations: implementations(project, read.held),
|
|
346
580
|
heldByInclusion: heldByInclusion(project, targetRoot),
|
|
581
|
+
generated: [...read.generated.keys()],
|
|
347
582
|
rooted: read.rooted,
|
|
348
|
-
directives: inlineDirectives(project, read.held, targetRoot),
|
|
583
|
+
directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
|
|
349
584
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
350
585
|
parts: projectParts(project, read.held, references, targetRoot),
|
|
351
586
|
}));
|
|
@@ -371,12 +606,13 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
371
606
|
stores: storesOf(projects.map((one) => ({
|
|
372
607
|
references: one.configured.references,
|
|
373
608
|
accessors: one.extra.accessors,
|
|
374
|
-
})),
|
|
609
|
+
})), swept.exempt, input.mode),
|
|
375
610
|
dependencies: dependenciesOf(host, targetRoot, projects.map((one) => one.extra.needs), swept.sweep.candidates.map((candidate) => candidate.id)),
|
|
376
611
|
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
377
612
|
files: {
|
|
378
613
|
tree: sourceTree(host, targetRoot),
|
|
379
614
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
615
|
+
generated: new Set(projects.flatMap((one) => one.extra.generated)),
|
|
380
616
|
},
|
|
381
617
|
boundary: {
|
|
382
618
|
consumers: {
|
|
@@ -387,7 +623,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
387
623
|
edges: readEdgeSides(host, targetRoot),
|
|
388
624
|
},
|
|
389
625
|
selfCheck,
|
|
390
|
-
intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...
|
|
626
|
+
intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
|
|
391
627
|
};
|
|
392
628
|
}
|
|
393
629
|
/**
|
|
@@ -424,10 +660,18 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
|
|
|
424
660
|
id: one.configured.configuration,
|
|
425
661
|
configFile: one.configFile,
|
|
426
662
|
})),
|
|
427
|
-
testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles)),
|
|
663
|
+
testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles), read.projects.flatMap((one) => one.configured.supportFiles ?? [])),
|
|
664
|
+
typeErrorSkips: read.projects.flatMap((one) => one.typeErrorSkips),
|
|
665
|
+
componentWarnings: [
|
|
666
|
+
...new Map(read.projects
|
|
667
|
+
.flatMap((one) => one.componentWarnings)
|
|
668
|
+
.map((one) => [`${one.path}:${String(one.line)}:${one.reason}`, one])).values(),
|
|
669
|
+
],
|
|
428
670
|
consumers: read.consumers,
|
|
429
671
|
notBuilt: read.notBuilt,
|
|
430
672
|
unanswered: read.unanswered,
|
|
673
|
+
componentsUnread: read.componentsUnread,
|
|
674
|
+
conventionsApplied: read.conventionsApplied,
|
|
431
675
|
},
|
|
432
676
|
configured: read.projects.map((one) => one.configured),
|
|
433
677
|
symbols,
|
|
@@ -475,6 +719,22 @@ export function runAnalysis(engine, host, scope, config, provenance, mode, detec
|
|
|
475
719
|
const inputs = reader.inputsUnder(readBaseline(host, scope.target.path, reader.symbols));
|
|
476
720
|
return analysisOf(reader, inputs);
|
|
477
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* One count per configuration of the run in which the checker left a question unanswered:
|
|
724
|
+
* the questions asked in its project, and the declarations of its own the run holds for them.
|
|
725
|
+
*/
|
|
726
|
+
export function unansweredCounts(analysis) {
|
|
727
|
+
const held = new Set(analysis.swept.heldByUnanswered);
|
|
728
|
+
return analysis.run.projects.flatMap((project) => {
|
|
729
|
+
const questions = analysis.run.unanswered.filter((one) => one.configFile === project.configFile).length;
|
|
730
|
+
if (questions === 0) {
|
|
731
|
+
return [];
|
|
732
|
+
}
|
|
733
|
+
const symbols = analysis.configured.find((one) => one.configuration === project.id)?.symbols;
|
|
734
|
+
const declarations = new Set((symbols ?? []).map((one) => one.id).filter((id) => held.has(id)));
|
|
735
|
+
return [{ configuration: project.id, questions, declarations: declarations.size }];
|
|
736
|
+
});
|
|
737
|
+
}
|
|
478
738
|
function analysisOf(reader, inputs) {
|
|
479
739
|
return {
|
|
480
740
|
result: findingsPass(inputs),
|