@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/src/analysis.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* project's checker, and that holds only for views the session itself hands out.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { lineOf, readComponent } from "./component-files.ts";
|
|
11
12
|
import type { Config, Provenance } from "./config.ts";
|
|
12
13
|
import {
|
|
13
14
|
consumerLoads,
|
|
@@ -33,12 +34,22 @@ import {
|
|
|
33
34
|
type LocatedDiagnostic,
|
|
34
35
|
type NotBuilt,
|
|
35
36
|
} from "./discover.ts";
|
|
37
|
+
import {
|
|
38
|
+
conventionFailuresFor,
|
|
39
|
+
generatedFiles,
|
|
40
|
+
readConventions,
|
|
41
|
+
type AppliedConvention,
|
|
42
|
+
type ConventionManifest,
|
|
43
|
+
} from "./conventions.ts";
|
|
36
44
|
import { readEdgeSides } from "./edges.ts";
|
|
37
45
|
import { enumGroup } from "./enum-group.ts";
|
|
46
|
+
import { generatedFile } from "./generated-file.ts";
|
|
47
|
+
import { sourceFilesOf } from "./source-files.ts";
|
|
38
48
|
import {
|
|
39
|
-
|
|
49
|
+
detectExemptions,
|
|
40
50
|
disabledClasses,
|
|
41
51
|
exemptionsOf,
|
|
52
|
+
holdingWhile,
|
|
42
53
|
isUnansweredRecord,
|
|
43
54
|
retainedIn,
|
|
44
55
|
type Detector,
|
|
@@ -63,6 +74,7 @@ import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-fun
|
|
|
63
74
|
import { inventory, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
64
75
|
import { readManifest } from "./manifest.ts";
|
|
65
76
|
import {
|
|
77
|
+
liveFilesAt,
|
|
66
78
|
matrixOf,
|
|
67
79
|
referenceKey,
|
|
68
80
|
sweepMatrix,
|
|
@@ -70,21 +82,32 @@ import {
|
|
|
70
82
|
type Matrix,
|
|
71
83
|
type SweepResult,
|
|
72
84
|
} from "./matrix.ts";
|
|
73
|
-
import { relativePath, resolvePath } from "./paths.ts";
|
|
85
|
+
import { joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
74
86
|
import type { UnansweredQuestion } from "./query.ts";
|
|
75
|
-
import { byPosition, type Position } from "./position.ts";
|
|
87
|
+
import { byPosition, isComponentFile, type Position } from "./position.ts";
|
|
88
|
+
import type { TypeErrorSkip, UnansweredCount } from "./report.ts";
|
|
89
|
+
import { SetupError, setupLine, type SetupFailure } from "./setup-failure.ts";
|
|
90
|
+
import {
|
|
91
|
+
pathsOf,
|
|
92
|
+
readErrors,
|
|
93
|
+
skippedRoots,
|
|
94
|
+
skippedUnits,
|
|
95
|
+
type SkippedUnit,
|
|
96
|
+
} from "./type-errors.ts";
|
|
76
97
|
import { reflectiveLookup } from "./reflective-lookup.ts";
|
|
77
98
|
import { references, testFileRulesOf, type Reference, type TestFileRule } from "./references.ts";
|
|
78
99
|
import { roots, unmatchedEverywhere, type RootKind, type Roots } from "./roots.ts";
|
|
79
100
|
import type { Scope } from "./scope.ts";
|
|
80
101
|
import { serializationContract } from "./serialization-contract.ts";
|
|
81
|
-
import {
|
|
102
|
+
import { supportReferences, testSupportFiles } from "./test-support.ts";
|
|
103
|
+
import { diagnosticsOf, ownedAs, runSession, type Engine, type ProjectView } from "./session.ts";
|
|
82
104
|
import { accessorsOf, storesOf } from "./stores.ts";
|
|
83
105
|
import { isPartKind, type SuppressionRecord, type Suppressions, type Verdict } from "./suppress.ts";
|
|
84
106
|
import { bindRows, readBaseline, readIgnoreFile, type Recorded } from "./suppress-file.ts";
|
|
85
107
|
import { inlineDirectives, inlineSuppressions, type InlineDirective } from "./suppress-inline.ts";
|
|
86
108
|
import type { Exemption, Mode, SweepInput } from "./sweep.ts";
|
|
87
109
|
import { readTemplates, templateField } from "./template-field.ts";
|
|
110
|
+
import { ownErrors, workspaceRun } from "./workspace-run.ts";
|
|
88
111
|
|
|
89
112
|
/** The setting whose source decides where a finding about a configured root sits. */
|
|
90
113
|
const ROOTS_SETTING = "roots.patterns";
|
|
@@ -156,6 +179,24 @@ interface ProjectRead {
|
|
|
156
179
|
readonly configFile: string;
|
|
157
180
|
readonly held: Inventory;
|
|
158
181
|
readonly rooted: Roots;
|
|
182
|
+
/**
|
|
183
|
+
* The paths below the target root of the workspace-package files the project holds
|
|
184
|
+
* only for the references they make.
|
|
185
|
+
*/
|
|
186
|
+
readonly referenceOnly: readonly string[];
|
|
187
|
+
/** Every type error of the project's own files that skipped a unit. */
|
|
188
|
+
readonly typeErrorSkips: readonly TypeErrorSkip[];
|
|
189
|
+
/** The lines each of those skips withholds every finding on. */
|
|
190
|
+
readonly skipped: readonly SkippedUnit[];
|
|
191
|
+
/** The project's generated files, by path below the target root, each with the row naming it. */
|
|
192
|
+
readonly generated: ReadonlyMap<string, string>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** One target project the run analyzes, as its stage is handed it. */
|
|
196
|
+
interface OpenedProject {
|
|
197
|
+
readonly configuration: string;
|
|
198
|
+
/** Enumerates and roots the project; called only inside the stage it was handed to. */
|
|
199
|
+
readonly read: () => ProjectRead;
|
|
159
200
|
}
|
|
160
201
|
|
|
161
202
|
/** What a run read of the consumers its scope declares. */
|
|
@@ -174,22 +215,48 @@ interface ReadProjects<Answer> {
|
|
|
174
215
|
readonly notBuilt: readonly NotBuilt[];
|
|
175
216
|
/** Every question the checker could not answer, each once. */
|
|
176
217
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
218
|
+
/** Per configuration whose component files the run cannot read, the reason. */
|
|
219
|
+
readonly componentsUnread: readonly ComponentsUnread[];
|
|
220
|
+
/** The convention rows the run applied. */
|
|
221
|
+
readonly conventionsApplied: readonly AppliedConvention[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The manifests the analysis reads, each once: the target's own, then every workspace
|
|
226
|
+
* member's below the target root.
|
|
227
|
+
*/
|
|
228
|
+
function conventionManifests(
|
|
229
|
+
host: Host,
|
|
230
|
+
targetRoot: string,
|
|
231
|
+
memberDirs: readonly string[],
|
|
232
|
+
): readonly ConventionManifest[] {
|
|
233
|
+
const found = new Map<string, ConventionManifest>();
|
|
234
|
+
if (host.kindOf(joinPath(targetRoot, "package.json")) === "file") {
|
|
235
|
+
found.set(targetRoot, { dir: targetRoot, path: "package.json" });
|
|
236
|
+
}
|
|
237
|
+
for (const dir of memberDirs) {
|
|
238
|
+
const below = relativePath(targetRoot, dir);
|
|
239
|
+
if (below !== undefined && !found.has(dir)) {
|
|
240
|
+
found.set(dir, { dir, path: `${below}/package.json` });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return [...found.values()];
|
|
177
244
|
}
|
|
178
245
|
|
|
179
246
|
/**
|
|
180
|
-
* Reads every project the scope discovers, and
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* {@link isGuess}); any other project
|
|
185
|
-
*
|
|
247
|
+
* Reads every project the scope discovers, and each declared consumer's, in one snapshot,
|
|
248
|
+
* each checked for errors first. `stage` is handed a target project and enumerates and roots
|
|
249
|
+
* it if it reads it; a consumer project is read for its references to the target. A type error
|
|
250
|
+
* in an own file skips the unit holding it. A guess that does not load or meets a setup failure
|
|
251
|
+
* is dropped into `notBuilt` (see {@link isGuess}); any other such project fails the run, as
|
|
252
|
+
* does a run whose every guess was dropped.
|
|
186
253
|
*/
|
|
187
254
|
function readProjects<Answer>(
|
|
188
255
|
engine: Engine,
|
|
189
256
|
host: Host,
|
|
190
257
|
scope: Scope,
|
|
191
258
|
config: Config,
|
|
192
|
-
stage: <Brand>(project: ProjectView<Brand>,
|
|
259
|
+
stage: <Brand>(project: ProjectView<Brand>, opened: OpenedProject) => Answer,
|
|
193
260
|
): ReadProjects<Answer> {
|
|
194
261
|
const targetRoot = scope.target.path;
|
|
195
262
|
const discovered = discoverProjects(engine, host, scope, config.analysis.configurations);
|
|
@@ -200,14 +267,31 @@ function readProjects<Answer>(
|
|
|
200
267
|
load.configFiles.map((configFile) => [configFile, load.consumer] as const),
|
|
201
268
|
),
|
|
202
269
|
);
|
|
270
|
+
const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
|
|
271
|
+
const workspace = workspaceRun(
|
|
272
|
+
engine,
|
|
273
|
+
host,
|
|
274
|
+
absoluteRoot,
|
|
275
|
+
discovered.configFiles,
|
|
276
|
+
readManifest(host, targetRoot),
|
|
277
|
+
);
|
|
278
|
+
const conventions = readConventions(
|
|
279
|
+
host,
|
|
280
|
+
(fileName, text) => engine.parseSourceFile(fileName, text),
|
|
281
|
+
conventionManifests(host, absoluteRoot, workspace?.memberDirs ?? []),
|
|
282
|
+
config.ts.disabledConventions,
|
|
283
|
+
);
|
|
203
284
|
const options = {
|
|
204
|
-
|
|
285
|
+
host,
|
|
286
|
+
manifest: workspace?.manifest ?? readManifest(host, targetRoot),
|
|
287
|
+
workspaceEntries: workspace?.entries,
|
|
205
288
|
patterns: config.rootPatterns,
|
|
206
289
|
entryFiles: config.ts.entryFiles,
|
|
290
|
+
conventions,
|
|
207
291
|
testFiles: config.ts.testFiles,
|
|
292
|
+
parse: (fileName: string, text: string) => engine.parseSourceFile(fileName, text),
|
|
208
293
|
publishedAPI: config.targetKind === "library",
|
|
209
294
|
};
|
|
210
|
-
const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
|
|
211
295
|
const notBuilt: NotBuilt[] = [...discovered.notBuilt];
|
|
212
296
|
const dropped = (configFile: string, error: string): void => {
|
|
213
297
|
notBuilt.push({
|
|
@@ -217,13 +301,42 @@ function readProjects<Answer>(
|
|
|
217
301
|
});
|
|
218
302
|
};
|
|
219
303
|
const failures: LocatedDiagnostic[] = [];
|
|
304
|
+
const unbuildable: SetupFailure[] = [];
|
|
305
|
+
const setupDropped: SetupFailure[] = [];
|
|
220
306
|
const consumed = new Map<string, Reference>();
|
|
307
|
+
const setupMet = (configFile: string, met: readonly SetupFailure[]): void => {
|
|
308
|
+
const [first] = met;
|
|
309
|
+
if (first !== undefined && isGuess(discovered, configFile)) {
|
|
310
|
+
dropped(configFile, setupLine(first));
|
|
311
|
+
setupDropped.push(...met);
|
|
312
|
+
} else {
|
|
313
|
+
unbuildable.push(...met);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
221
316
|
const session = runSession(
|
|
222
317
|
engine,
|
|
223
318
|
[...discovered.configFiles, ...consumerOf.keys()],
|
|
224
|
-
(
|
|
225
|
-
const
|
|
226
|
-
const
|
|
319
|
+
(opened) => {
|
|
320
|
+
const consumer = consumerOf.get(opened.configFile);
|
|
321
|
+
const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
|
|
322
|
+
if (setup !== undefined) {
|
|
323
|
+
setupMet(opened.configFile, [
|
|
324
|
+
{ setupClass: "workspace-member-without-source", detail: setup },
|
|
325
|
+
]);
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const referenceOnly =
|
|
329
|
+
workspace === undefined || consumer !== undefined
|
|
330
|
+
? () => false
|
|
331
|
+
: workspace.referenceOnly(opened.configFile, opened.rootFiles);
|
|
332
|
+
const project =
|
|
333
|
+
workspace === undefined || consumer !== undefined
|
|
334
|
+
? opened
|
|
335
|
+
: ownedAs(opened, (file, fromExternalLibrary) =>
|
|
336
|
+
workspace.ownFile(file, fromExternalLibrary),
|
|
337
|
+
);
|
|
338
|
+
const raw = projectErrors(diagnosticsOf(project));
|
|
339
|
+
const errors = { load: raw.load, check: ownErrors(raw.check, referenceOnly) };
|
|
227
340
|
const [unloaded] = errors.load;
|
|
228
341
|
if (
|
|
229
342
|
unloaded !== undefined &&
|
|
@@ -236,8 +349,45 @@ function readProjects<Answer>(
|
|
|
236
349
|
);
|
|
237
350
|
return undefined;
|
|
238
351
|
}
|
|
239
|
-
if (errors.load.length > 0
|
|
240
|
-
failures.push(...errors.load
|
|
352
|
+
if (errors.load.length > 0) {
|
|
353
|
+
failures.push(...errors.load);
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
const reading = readErrors(project, errors.check, {
|
|
357
|
+
host,
|
|
358
|
+
targetRoot:
|
|
359
|
+
consumer === undefined
|
|
360
|
+
? absoluteRoot
|
|
361
|
+
: resolvePath(host.workingDirectory(), consumer.path),
|
|
362
|
+
paths: pathsOf(engine, project.configFile),
|
|
363
|
+
});
|
|
364
|
+
if (reading.failing.length > 0) {
|
|
365
|
+
failures.push(...reading.failing);
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
if (consumer !== undefined && reading.missing.length > 0) {
|
|
369
|
+
unbuildable.push(
|
|
370
|
+
...reading.missing.map((one) => ({
|
|
371
|
+
setupClass: "missing-consumer" as const,
|
|
372
|
+
detail: `the consumer ${consumer.path} does not build, its dependencies not installed: ${one.detail}`,
|
|
373
|
+
})),
|
|
374
|
+
);
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
if (reading.missing.length > 0) {
|
|
378
|
+
setupMet(project.configFile, reading.missing);
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
const unconventional =
|
|
382
|
+
consumer === undefined
|
|
383
|
+
? conventionFailuresFor(
|
|
384
|
+
conventions,
|
|
385
|
+
project.configFile,
|
|
386
|
+
project.ownSourceFiles().map((file) => file.fileName),
|
|
387
|
+
)
|
|
388
|
+
: [];
|
|
389
|
+
if (unconventional.length > 0) {
|
|
390
|
+
setupMet(project.configFile, unconventional);
|
|
241
391
|
return undefined;
|
|
242
392
|
}
|
|
243
393
|
if (consumer !== undefined) {
|
|
@@ -255,12 +405,39 @@ function readProjects<Answer>(
|
|
|
255
405
|
}
|
|
256
406
|
return undefined;
|
|
257
407
|
}
|
|
258
|
-
const held = inventory(project, host, targetRoot);
|
|
259
|
-
const rooted = roots(project, held, targetRoot, options);
|
|
260
408
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
261
|
-
|
|
409
|
+
const read = (): ProjectRead => {
|
|
410
|
+
const held = inventory(project, host, targetRoot);
|
|
411
|
+
const found = roots(project, held, targetRoot, options);
|
|
412
|
+
const kept = skippedRoots(project, held, reading.skips);
|
|
413
|
+
const rooted =
|
|
414
|
+
kept.length === 0
|
|
415
|
+
? found
|
|
416
|
+
: {
|
|
417
|
+
...found,
|
|
418
|
+
liveUnderReachability: [
|
|
419
|
+
...found.liveUnderReachability,
|
|
420
|
+
...kept.map((id) => ({ id, kind: "type-error" as const, source: "" })),
|
|
421
|
+
],
|
|
422
|
+
};
|
|
423
|
+
return {
|
|
424
|
+
configuration,
|
|
425
|
+
configFile: project.configFile,
|
|
426
|
+
held,
|
|
427
|
+
rooted,
|
|
428
|
+
typeErrorSkips: reading.skips.map((one) => one.record),
|
|
429
|
+
skipped: skippedUnits(reading.skips, absoluteRoot),
|
|
430
|
+
referenceOnly: project
|
|
431
|
+
.ownSourceFiles()
|
|
432
|
+
.filter((file) => referenceOnly(file.fileName))
|
|
433
|
+
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
434
|
+
generated: generatedFiles(conventions, sourceFilesOf(project, targetRoot)),
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
return stage(project, { configuration, read });
|
|
262
438
|
},
|
|
263
439
|
discovered.derived ? "record" : "refuse",
|
|
440
|
+
workspace?.programs,
|
|
264
441
|
);
|
|
265
442
|
for (const configFile of session.unopened) {
|
|
266
443
|
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
@@ -268,6 +445,9 @@ function readProjects<Answer>(
|
|
|
268
445
|
}
|
|
269
446
|
dropped(configFile, "the compiler opened no project for the configuration");
|
|
270
447
|
}
|
|
448
|
+
if (unbuildable.length > 0) {
|
|
449
|
+
throw new SetupError(unbuildable);
|
|
450
|
+
}
|
|
271
451
|
if (failures.length > 0) {
|
|
272
452
|
throw new DiscoveryError(
|
|
273
453
|
`${String(failures.length)} error(s), so no answer was produced`,
|
|
@@ -276,6 +456,9 @@ function readProjects<Answer>(
|
|
|
276
456
|
}
|
|
277
457
|
const answers = session.projects.filter((answer): answer is Answer => answer !== undefined);
|
|
278
458
|
const [first] = notBuilt;
|
|
459
|
+
if (answers.length === 0 && setupDropped.length > 0) {
|
|
460
|
+
throw new SetupError(setupDropped);
|
|
461
|
+
}
|
|
279
462
|
if (answers.length === 0 && first !== undefined) {
|
|
280
463
|
throw new DiscoveryError(
|
|
281
464
|
`no configuration discovery derived could be built; ${first.id}: ${first.error}`,
|
|
@@ -290,9 +473,37 @@ function readProjects<Answer>(
|
|
|
290
473
|
},
|
|
291
474
|
notBuilt,
|
|
292
475
|
unanswered: session.unanswered,
|
|
476
|
+
componentsUnread: workspace?.componentsUnread ?? [],
|
|
477
|
+
conventionsApplied: conventions.applied,
|
|
293
478
|
};
|
|
294
479
|
}
|
|
295
480
|
|
|
481
|
+
/** The projects one run analyzes, and what reading them left out. */
|
|
482
|
+
interface RunProjects {
|
|
483
|
+
/** Each configuration the run analyzes, by the name discovery gives its project, in discovery order. */
|
|
484
|
+
readonly configurations: readonly string[];
|
|
485
|
+
/** The derived configurations the run dropped. */
|
|
486
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
487
|
+
/** Every question the checker could not answer, each once. */
|
|
488
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* The projects the run the scope and the configuration describe analyzes, each read
|
|
493
|
+
* for the errors and setup failures that decide whether it is analyzed, dropped or
|
|
494
|
+
* fails the run, exactly as the analysis reads them.
|
|
495
|
+
*/
|
|
496
|
+
export function runProjects(engine: Engine, host: Host, scope: Scope, config: Config): RunProjects {
|
|
497
|
+
const read = readProjects(
|
|
498
|
+
engine,
|
|
499
|
+
host,
|
|
500
|
+
scope,
|
|
501
|
+
config,
|
|
502
|
+
(_project, opened) => opened.configuration,
|
|
503
|
+
);
|
|
504
|
+
return { configurations: read.answers, notBuilt: read.notBuilt, unanswered: read.unanswered };
|
|
505
|
+
}
|
|
506
|
+
|
|
296
507
|
/**
|
|
297
508
|
* The root set of the run the scope and the configuration describe.
|
|
298
509
|
*
|
|
@@ -307,7 +518,7 @@ export function runRoots(
|
|
|
307
518
|
provenance: Provenance,
|
|
308
519
|
): RunRoots {
|
|
309
520
|
const targetRoot = scope.target.path;
|
|
310
|
-
const read = readProjects(engine, host, scope, config, (_project,
|
|
521
|
+
const read = readProjects(engine, host, scope, config, (_project, opened) => opened.read());
|
|
311
522
|
const projects = read.answers;
|
|
312
523
|
|
|
313
524
|
const configurations: string[] = [];
|
|
@@ -361,6 +572,8 @@ export function runRoots(
|
|
|
361
572
|
export interface RunSweep {
|
|
362
573
|
readonly matrix: Matrix;
|
|
363
574
|
readonly sweep: SweepResult;
|
|
575
|
+
/** The exemption records that hold under the sweep. */
|
|
576
|
+
readonly exempt: readonly Exemption[];
|
|
364
577
|
/**
|
|
365
578
|
* The exemption records that held back a declaration some configuration of the run
|
|
366
579
|
* would otherwise judge dead, in the declarations' site order.
|
|
@@ -375,12 +588,25 @@ export interface RunSweep {
|
|
|
375
588
|
* such a declaration keeps live. The run reports none of them.
|
|
376
589
|
*/
|
|
377
590
|
readonly heldByUnanswered: readonly string[];
|
|
591
|
+
/** The lines of the units type errors skipped, on which the run reports nothing. */
|
|
592
|
+
readonly skipped: readonly SkippedUnit[];
|
|
593
|
+
/** The component files the run read, by path below the target root, each once. */
|
|
594
|
+
readonly componentFiles: readonly string[];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** One `<script` start tag of a component file the run read no block for. */
|
|
598
|
+
export interface ComponentWarning {
|
|
599
|
+
/** The file, below the target root. */
|
|
600
|
+
readonly path: string;
|
|
601
|
+
readonly line: number;
|
|
602
|
+
readonly reason: string;
|
|
378
603
|
}
|
|
379
604
|
|
|
380
605
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
381
606
|
const DETECTORS: Detectors = new Map<TSExemptionClass, Detector>([
|
|
382
607
|
["interface-satisfaction", interfaceSatisfaction],
|
|
383
608
|
["enum-group", enumGroup],
|
|
609
|
+
["generated-file", generatedFile],
|
|
384
610
|
["template-field", templateField],
|
|
385
611
|
["reflective-lookup", reflectiveLookup],
|
|
386
612
|
["decorator", decorator],
|
|
@@ -396,6 +622,11 @@ interface SweptProject<Extra> {
|
|
|
396
622
|
readonly exempt: readonly Exemption[];
|
|
397
623
|
/** The declarations of the project an unanswered question could have kept live. */
|
|
398
624
|
readonly unanswered: readonly string[];
|
|
625
|
+
readonly typeErrorSkips: readonly TypeErrorSkip[];
|
|
626
|
+
readonly skipped: readonly SkippedUnit[];
|
|
627
|
+
/** The component files the project holds of its own, by path below the target root. */
|
|
628
|
+
readonly componentFiles: readonly string[];
|
|
629
|
+
readonly componentWarnings: readonly ComponentWarning[];
|
|
399
630
|
readonly extra: Extra;
|
|
400
631
|
}
|
|
401
632
|
|
|
@@ -412,6 +643,10 @@ interface ReadRun<Extra> {
|
|
|
412
643
|
readonly notBuilt: readonly NotBuilt[];
|
|
413
644
|
/** Every question the checker could not answer, each once. */
|
|
414
645
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
646
|
+
/** Per configuration whose component files the run cannot read, the reason. */
|
|
647
|
+
readonly componentsUnread: readonly ComponentsUnread[];
|
|
648
|
+
/** The convention rows the run applied. */
|
|
649
|
+
readonly conventionsApplied: readonly AppliedConvention[];
|
|
415
650
|
}
|
|
416
651
|
|
|
417
652
|
/**
|
|
@@ -439,48 +674,78 @@ function readRun<Extra>(
|
|
|
439
674
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
440
675
|
: readTemplates(host, targetRoot, config.analysis);
|
|
441
676
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
442
|
-
const read = readProjects<SweptProject<Extra>>(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
...
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
677
|
+
const read = readProjects<SweptProject<Extra>>(engine, host, scope, config, (project, opened) => {
|
|
678
|
+
const projectRead = opened.read();
|
|
679
|
+
const resolved = references(project, projectRead.held, targetRoot, {
|
|
680
|
+
testFiles: config.ts.testFiles,
|
|
681
|
+
});
|
|
682
|
+
const support = testSupportFiles(
|
|
683
|
+
projectRead.held.symbols,
|
|
684
|
+
resolved.references,
|
|
685
|
+
projectRead.rooted.liveUnderReachability,
|
|
686
|
+
resolved.testFilePaths,
|
|
687
|
+
);
|
|
688
|
+
const detected = detectExemptions(
|
|
689
|
+
{
|
|
690
|
+
project,
|
|
691
|
+
held: projectRead.held,
|
|
692
|
+
targetRoot,
|
|
693
|
+
templates,
|
|
694
|
+
ts: config.ts,
|
|
695
|
+
consumers,
|
|
696
|
+
...(config.analysis.generatedFiles === "include"
|
|
697
|
+
? {}
|
|
698
|
+
: { generated: projectRead.generated }),
|
|
699
|
+
},
|
|
700
|
+
detectors,
|
|
701
|
+
{
|
|
702
|
+
disabled,
|
|
703
|
+
mode,
|
|
704
|
+
testFiles: new Set([...resolved.testFilePaths, ...support]),
|
|
705
|
+
},
|
|
706
|
+
);
|
|
707
|
+
const exempt = detected.records;
|
|
708
|
+
const made = supportReferences(resolved.references, support);
|
|
709
|
+
const configured: Configured = {
|
|
710
|
+
configuration: projectRead.configuration,
|
|
711
|
+
symbols: projectRead.held.symbols,
|
|
712
|
+
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
713
|
+
roots: projectRead.rooted.liveUnderReachability,
|
|
714
|
+
testFiles: resolved.testFilePaths,
|
|
715
|
+
...(support.size === 0 ? {} : { supportFiles: [...support].sort() }),
|
|
716
|
+
...(projectRead.referenceOnly.length === 0
|
|
717
|
+
? {}
|
|
718
|
+
: { referenceOnly: projectRead.referenceOnly }),
|
|
719
|
+
};
|
|
720
|
+
const unanswered = [
|
|
721
|
+
...projectRead.held.unanswered,
|
|
722
|
+
...projectRead.rooted.unanswered,
|
|
723
|
+
...made
|
|
724
|
+
.filter((reference) => reference.resolution === "by-name")
|
|
725
|
+
.map((reference) => reference.to),
|
|
726
|
+
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
727
|
+
];
|
|
728
|
+
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
729
|
+
const pathOf = (file: (typeof components)[number]): string =>
|
|
730
|
+
relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
731
|
+
return {
|
|
732
|
+
configFile: projectRead.configFile,
|
|
733
|
+
configured,
|
|
734
|
+
exempt,
|
|
735
|
+
unanswered,
|
|
736
|
+
typeErrorSkips: projectRead.typeErrorSkips,
|
|
737
|
+
skipped: projectRead.skipped,
|
|
738
|
+
componentFiles: components.map(pathOf),
|
|
739
|
+
componentWarnings: components.flatMap((file) =>
|
|
740
|
+
readComponent(file.originalText).warnings.map((warning) => ({
|
|
741
|
+
path: pathOf(file),
|
|
742
|
+
line: lineOf(file.originalText, warning.offset),
|
|
743
|
+
reason: warning.reason,
|
|
744
|
+
})),
|
|
745
|
+
),
|
|
746
|
+
extra: extra(project, projectRead, made),
|
|
747
|
+
};
|
|
748
|
+
});
|
|
484
749
|
const consumed = read.consumers.references;
|
|
485
750
|
const projects = read.answers.map((one) =>
|
|
486
751
|
consumed.length === 0
|
|
@@ -506,9 +771,93 @@ function readRun<Extra>(
|
|
|
506
771
|
consumers: read.consumers.loaded,
|
|
507
772
|
notBuilt: read.notBuilt,
|
|
508
773
|
unanswered: read.unanswered,
|
|
774
|
+
componentsUnread: read.componentsUnread,
|
|
775
|
+
conventionsApplied: read.conventionsApplied,
|
|
509
776
|
};
|
|
510
777
|
}
|
|
511
778
|
|
|
779
|
+
/**
|
|
780
|
+
* The run swept under one set of marks with the records that hold. A record a component
|
|
781
|
+
* file's markup holds counts only while the file is live, and what it holds may be what
|
|
782
|
+
* keeps the file live, so each file's markup joins only once a sweep without it finds the
|
|
783
|
+
* file live, until a sweep finds no further file live.
|
|
784
|
+
*/
|
|
785
|
+
function holdingSweep<Extra>(
|
|
786
|
+
read: ReadRun<Extra>,
|
|
787
|
+
input: Pick<SweepInput, "marked" | "mode">,
|
|
788
|
+
): { readonly swept: SweepInput; readonly result: SweepResult } {
|
|
789
|
+
const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
|
|
790
|
+
let live = new Set<string>();
|
|
791
|
+
for (;;) {
|
|
792
|
+
const swept: SweepInput = {
|
|
793
|
+
...input,
|
|
794
|
+
exempt: holdingWhile(read.exempt, live),
|
|
795
|
+
unanswered: read.unansweredHeld,
|
|
796
|
+
};
|
|
797
|
+
const result = sweepMatrix(read.matrix, swept);
|
|
798
|
+
const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
|
|
799
|
+
if (next.size === live.size) {
|
|
800
|
+
return { swept, result };
|
|
801
|
+
}
|
|
802
|
+
live = next;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* The test references exemption evidence written in a test file makes: each is a read of
|
|
808
|
+
* the declaration it would retain, made by the test file at the evidence's site.
|
|
809
|
+
*/
|
|
810
|
+
function evidenceReferences(
|
|
811
|
+
symbols: readonly InventorySymbol[],
|
|
812
|
+
inTestFiles: readonly Exemption[],
|
|
813
|
+
): readonly Reference[] {
|
|
814
|
+
if (inTestFiles.length === 0) {
|
|
815
|
+
return [];
|
|
816
|
+
}
|
|
817
|
+
const files = new Map(
|
|
818
|
+
symbols
|
|
819
|
+
.filter((symbol) => symbol.kind === "file")
|
|
820
|
+
.map((symbol) => [symbol.position.path, symbol.id]),
|
|
821
|
+
);
|
|
822
|
+
return inTestFiles.flatMap((record) => {
|
|
823
|
+
const from = files.get(record.site.path);
|
|
824
|
+
return from === undefined
|
|
825
|
+
? []
|
|
826
|
+
: [
|
|
827
|
+
{
|
|
828
|
+
from,
|
|
829
|
+
to: record.id,
|
|
830
|
+
position: record.site,
|
|
831
|
+
use: "read",
|
|
832
|
+
resolution: "syntax",
|
|
833
|
+
test: true,
|
|
834
|
+
},
|
|
835
|
+
];
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* The declarations every reference the mode counts stores into, a loaded consumer's
|
|
841
|
+
* reference counting as a read: what the write-only kind would report but for a record.
|
|
842
|
+
*/
|
|
843
|
+
function writtenOnly<Extra>(read: ReadRun<Extra>, mode: Mode): ReadonlySet<string> {
|
|
844
|
+
const written = new Set<string>();
|
|
845
|
+
const readSome = new Set<string>();
|
|
846
|
+
for (const { configured } of read.projects) {
|
|
847
|
+
for (const reference of configured.references) {
|
|
848
|
+
if (mode.production && reference.test) {
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (reference.use === "write" && reference.consumer === undefined) {
|
|
852
|
+
written.add(reference.to);
|
|
853
|
+
} else {
|
|
854
|
+
readSome.add(reference.to);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return new Set([...written].filter((id) => !readSome.has(id)));
|
|
859
|
+
}
|
|
860
|
+
|
|
512
861
|
/**
|
|
513
862
|
* The sweep of a run read once, under one set of marks. Where a question went unanswered,
|
|
514
863
|
* the run is swept once more without what that question could have kept live and without
|
|
@@ -520,8 +869,8 @@ function sweptOf<Extra>(
|
|
|
520
869
|
read: ReadRun<Extra>,
|
|
521
870
|
input: Pick<SweepInput, "marked" | "mode">,
|
|
522
871
|
): RunSweep {
|
|
523
|
-
const
|
|
524
|
-
const
|
|
872
|
+
const { swept, result } = holdingSweep(read, input);
|
|
873
|
+
const exempt = swept.exempt ?? [];
|
|
525
874
|
const held = new Set(read.unansweredHeld);
|
|
526
875
|
if (held.size > 0) {
|
|
527
876
|
// What a declaration reads where the checker did not answer is unknown, so a rule
|
|
@@ -542,7 +891,7 @@ function sweptOf<Extra>(
|
|
|
542
891
|
);
|
|
543
892
|
const bare: SweepInput = {
|
|
544
893
|
...swept,
|
|
545
|
-
exempt:
|
|
894
|
+
exempt: exempt.filter((record) => !isUnansweredRecord(record)),
|
|
546
895
|
unanswered: [],
|
|
547
896
|
};
|
|
548
897
|
for (const candidate of sweepMatrix(answered, bare).candidates) {
|
|
@@ -558,10 +907,13 @@ function sweptOf<Extra>(
|
|
|
558
907
|
return {
|
|
559
908
|
matrix: read.matrix,
|
|
560
909
|
sweep: result,
|
|
561
|
-
|
|
910
|
+
exempt,
|
|
911
|
+
retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
|
|
562
912
|
notBuilt: read.notBuilt,
|
|
563
913
|
unanswered: read.unanswered,
|
|
564
914
|
heldByUnanswered: [...held],
|
|
915
|
+
skipped: read.projects.flatMap((one) => one.skipped),
|
|
916
|
+
componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(compare),
|
|
565
917
|
};
|
|
566
918
|
}
|
|
567
919
|
|
|
@@ -596,6 +948,7 @@ interface EmitterExtra {
|
|
|
596
948
|
readonly needs: ReturnType<typeof projectNeeds>;
|
|
597
949
|
readonly implementations: ReturnType<typeof implementations>;
|
|
598
950
|
readonly heldByInclusion: readonly string[];
|
|
951
|
+
readonly generated: readonly string[];
|
|
599
952
|
readonly rooted: Roots;
|
|
600
953
|
readonly directives: readonly InlineDirective[];
|
|
601
954
|
readonly declarationsNamed: readonly boolean[];
|
|
@@ -622,11 +975,16 @@ function readEmitterRun(
|
|
|
622
975
|
(project, read, references) => ({
|
|
623
976
|
deprecation: deprecatedDeclarations(project, read.held),
|
|
624
977
|
accessors: accessorsOf(project, read.held),
|
|
625
|
-
needs: projectNeeds(project, read.held, host)
|
|
978
|
+
needs: projectNeeds(project, read.held, host, targetRoot, (fileName, text) =>
|
|
979
|
+
engine.parseSourceFile(fileName, text),
|
|
980
|
+
),
|
|
626
981
|
implementations: implementations(project, read.held),
|
|
627
982
|
heldByInclusion: heldByInclusion(project, targetRoot),
|
|
983
|
+
generated: [...read.generated.keys()],
|
|
628
984
|
rooted: read.rooted,
|
|
629
|
-
directives: inlineDirectives(project, read.held, targetRoot)
|
|
985
|
+
directives: inlineDirectives(project, read.held, targetRoot).filter(
|
|
986
|
+
(directive) => !read.referenceOnly.includes(directive.site.path),
|
|
987
|
+
),
|
|
630
988
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
631
989
|
parts: projectParts(project, read.held, references, targetRoot),
|
|
632
990
|
}),
|
|
@@ -663,7 +1021,7 @@ function emitterInputOver(
|
|
|
663
1021
|
references: one.configured.references,
|
|
664
1022
|
accessors: one.extra.accessors,
|
|
665
1023
|
})),
|
|
666
|
-
|
|
1024
|
+
swept.exempt,
|
|
667
1025
|
input.mode,
|
|
668
1026
|
),
|
|
669
1027
|
dependencies: dependenciesOf(
|
|
@@ -676,6 +1034,7 @@ function emitterInputOver(
|
|
|
676
1034
|
files: {
|
|
677
1035
|
tree: sourceTree(host, targetRoot),
|
|
678
1036
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
1037
|
+
generated: new Set(projects.flatMap((one) => one.extra.generated)),
|
|
679
1038
|
},
|
|
680
1039
|
boundary: {
|
|
681
1040
|
consumers: {
|
|
@@ -689,7 +1048,7 @@ function emitterInputOver(
|
|
|
689
1048
|
intraFunction: intraFunctionFacts(
|
|
690
1049
|
projects.map((one) => one.extra.parts),
|
|
691
1050
|
projects.flatMap((one) => one.configured.references),
|
|
692
|
-
new Set([...
|
|
1051
|
+
new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered]),
|
|
693
1052
|
),
|
|
694
1053
|
};
|
|
695
1054
|
}
|
|
@@ -768,10 +1127,21 @@ function readAnalysis(
|
|
|
768
1127
|
testFileRules: testFileRulesOf(
|
|
769
1128
|
config.ts.testFiles,
|
|
770
1129
|
read.projects.flatMap((one) => one.configured.testFiles),
|
|
1130
|
+
read.projects.flatMap((one) => one.configured.supportFiles ?? []),
|
|
771
1131
|
),
|
|
1132
|
+
typeErrorSkips: read.projects.flatMap((one) => one.typeErrorSkips),
|
|
1133
|
+
componentWarnings: [
|
|
1134
|
+
...new Map(
|
|
1135
|
+
read.projects
|
|
1136
|
+
.flatMap((one) => one.componentWarnings)
|
|
1137
|
+
.map((one) => [`${one.path}:${String(one.line)}:${one.reason}`, one] as const),
|
|
1138
|
+
).values(),
|
|
1139
|
+
],
|
|
772
1140
|
consumers: read.consumers,
|
|
773
1141
|
notBuilt: read.notBuilt,
|
|
774
1142
|
unanswered: read.unanswered,
|
|
1143
|
+
componentsUnread: read.componentsUnread,
|
|
1144
|
+
conventionsApplied: read.conventionsApplied,
|
|
775
1145
|
},
|
|
776
1146
|
configured: read.projects.map((one) => one.configured),
|
|
777
1147
|
symbols,
|
|
@@ -842,6 +1212,20 @@ export interface RunFacts {
|
|
|
842
1212
|
readonly notBuilt: readonly NotBuilt[];
|
|
843
1213
|
/** Every question the checker could not answer, each once. */
|
|
844
1214
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
1215
|
+
/** Every type error that skipped a unit, in the run's order. */
|
|
1216
|
+
readonly typeErrorSkips: readonly TypeErrorSkip[];
|
|
1217
|
+
/** Every `<script` start tag a component file holds that no block was read for, each once. */
|
|
1218
|
+
readonly componentWarnings: readonly ComponentWarning[];
|
|
1219
|
+
/** Per configuration whose component files the run cannot read, the reason. */
|
|
1220
|
+
readonly componentsUnread: readonly ComponentsUnread[];
|
|
1221
|
+
/** The convention rows the run applied. */
|
|
1222
|
+
readonly conventionsApplied: readonly AppliedConvention[];
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/** One configuration whose component files a run cannot read. */
|
|
1226
|
+
export interface ComponentsUnread {
|
|
1227
|
+
readonly configFile: string;
|
|
1228
|
+
readonly reason: string;
|
|
845
1229
|
}
|
|
846
1230
|
|
|
847
1231
|
/** The findings of one run, beside what a report states about the run itself. */
|
|
@@ -874,6 +1258,25 @@ export function runAnalysis(
|
|
|
874
1258
|
return analysisOf(reader, inputs);
|
|
875
1259
|
}
|
|
876
1260
|
|
|
1261
|
+
/**
|
|
1262
|
+
* One count per configuration of the run in which the checker left a question unanswered:
|
|
1263
|
+
* the questions asked in its project, and the declarations of its own the run holds for them.
|
|
1264
|
+
*/
|
|
1265
|
+
export function unansweredCounts(analysis: RunAnalysis): readonly UnansweredCount[] {
|
|
1266
|
+
const held = new Set(analysis.swept.heldByUnanswered);
|
|
1267
|
+
return analysis.run.projects.flatMap((project) => {
|
|
1268
|
+
const questions = analysis.run.unanswered.filter(
|
|
1269
|
+
(one) => one.configFile === project.configFile,
|
|
1270
|
+
).length;
|
|
1271
|
+
if (questions === 0) {
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
const symbols = analysis.configured.find((one) => one.configuration === project.id)?.symbols;
|
|
1275
|
+
const declarations = new Set((symbols ?? []).map((one) => one.id).filter((id) => held.has(id)));
|
|
1276
|
+
return [{ configuration: project.id, questions, declarations: declarations.size }];
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
|
|
877
1280
|
function analysisOf(reader: AnalysisReader, inputs: AnalysisInputs): RunAnalysis {
|
|
878
1281
|
return {
|
|
879
1282
|
result: findingsPass(inputs),
|