@cplieger/deadset-ts 3.10.0 → 3.12.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 +13 -13
- package/dist/src/analysis.js +56 -16
- package/dist/src/config.js +6 -0
- package/dist/src/conformance-record.js +2 -12
- package/dist/src/consumers.js +49 -0
- package/dist/src/discover.js +59 -23
- package/dist/src/findings/completion.js +49 -17
- package/dist/src/findings/non-code-artifacts.js +9 -2
- package/dist/src/findings/reads-and-writes.js +3 -1
- package/dist/src/findings-pass.js +3 -5
- package/dist/src/graph.js +8 -5
- package/dist/src/matrix.js +14 -3
- package/dist/src/references.js +21 -9
- package/dist/src/report.js +4 -1
- package/dist/src/reporters/annotations.js +36 -0
- package/dist/src/reporters/fingerprint.js +61 -0
- package/dist/src/reporters/reporter.js +7 -1
- package/dist/src/reporters/reporters.js +18 -0
- package/dist/src/reporters/rules.js +178 -0
- package/dist/src/reporters/sarif.js +235 -0
- package/dist/src/reporters/sha256.js +101 -0
- package/dist/src/reporters/template.js +877 -0
- package/dist/src/run.js +4 -10
- package/dist/src/session.js +15 -0
- package/dist/src/sweep.js +11 -0
- package/dist/src/verbs/analyze.js +48 -16
- package/dist/src/verbs/explain.js +317 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +123 -29
- package/src/config.ts +8 -0
- package/src/conformance-record.ts +2 -14
- package/src/consumers.ts +86 -0
- package/src/discover.ts +67 -23
- package/src/findings/completion.ts +56 -20
- package/src/findings/non-code-artifacts.ts +9 -2
- package/src/findings/reads-and-writes.ts +3 -1
- package/src/findings-pass.ts +5 -7
- package/src/graph.ts +14 -5
- package/src/matrix.ts +14 -5
- package/src/references.ts +48 -9
- package/src/report.ts +17 -2
- package/src/reporters/annotations.ts +67 -0
- package/src/reporters/fingerprint.ts +69 -0
- package/src/reporters/json.ts +2 -2
- package/src/reporters/reporter.ts +21 -2
- package/src/reporters/reporters.ts +18 -1
- package/src/reporters/rules.ts +195 -0
- package/src/reporters/sarif.ts +287 -0
- package/src/reporters/sha256.ts +120 -0
- package/src/reporters/template.ts +989 -0
- package/src/reporters/text.ts +2 -3
- package/src/run.ts +5 -11
- package/src/session.ts +19 -0
- package/src/sweep.ts +13 -1
- package/src/verbs/analyze.ts +60 -19
- package/src/verbs/explain.ts +380 -0
package/src/analysis.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Diagnostic } from "@typescript/native/unstable/sync";
|
|
12
12
|
import type { Config, Provenance } from "./config.ts";
|
|
13
|
+
import {
|
|
14
|
+
consumerLoads,
|
|
15
|
+
consumerReferences,
|
|
16
|
+
moduleIdentity,
|
|
17
|
+
type LoadedConsumer,
|
|
18
|
+
} from "./consumers.ts";
|
|
13
19
|
import { decorator } from "./decorator.ts";
|
|
14
20
|
import { dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
15
21
|
import { deprecatedDeclarations } from "./deprecation.ts";
|
|
@@ -41,11 +47,18 @@ import { injectionContainer } from "./injection-container.ts";
|
|
|
41
47
|
import { interfaceSatisfaction } from "./interface-satisfaction.ts";
|
|
42
48
|
import { inventory, type Inventory } from "./inventory.ts";
|
|
43
49
|
import { readManifest } from "./manifest.ts";
|
|
44
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
matrixOf,
|
|
52
|
+
referenceKey,
|
|
53
|
+
sweepMatrix,
|
|
54
|
+
type Configured,
|
|
55
|
+
type Matrix,
|
|
56
|
+
type SweepResult,
|
|
57
|
+
} from "./matrix.ts";
|
|
45
58
|
import { relativePath, resolvePath } from "./paths.ts";
|
|
46
59
|
import { byPosition, type Position } from "./position.ts";
|
|
47
60
|
import { reflectiveLookup } from "./reflective-lookup.ts";
|
|
48
|
-
import { references, testFileRulesOf, type TestFileRule } from "./references.ts";
|
|
61
|
+
import { references, testFileRulesOf, type Reference, type TestFileRule } from "./references.ts";
|
|
49
62
|
import { roots, unmatchedEverywhere, type RootKind, type Roots } from "./roots.ts";
|
|
50
63
|
import type { Scope } from "./scope.ts";
|
|
51
64
|
import { serializationContract } from "./serialization-contract.ts";
|
|
@@ -125,11 +138,27 @@ interface ProjectRead {
|
|
|
125
138
|
readonly rooted: Roots;
|
|
126
139
|
}
|
|
127
140
|
|
|
141
|
+
/** What a run read of the consumers its scope declares. */
|
|
142
|
+
interface ConsumerReads {
|
|
143
|
+
/** Every declared consumer, each loaded, in the scope's order. */
|
|
144
|
+
readonly loaded: readonly LoadedConsumer[];
|
|
145
|
+
/** Every reference a consumer makes to a declaration of the target, each once. */
|
|
146
|
+
readonly references: readonly Reference[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** What a run read of its projects: each target project's answer, and its consumers. */
|
|
150
|
+
interface ReadProjects<Answer> {
|
|
151
|
+
readonly answers: readonly Answer[];
|
|
152
|
+
readonly consumers: ConsumerReads;
|
|
153
|
+
}
|
|
154
|
+
|
|
128
155
|
/**
|
|
129
|
-
* Reads every project the scope discovers,
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
156
|
+
* Reads every project the scope discovers, and every project of every consumer it
|
|
157
|
+
* declares, in one snapshot. Each is checked for errors first. A target project is then
|
|
158
|
+
* enumerated and rooted, and `stage` reads the rest of what it needs from the project
|
|
159
|
+
* while its view is open; a consumer project is read for its references to the target's
|
|
160
|
+
* declarations. A project carrying an error fails the run with no answer, as every verb
|
|
161
|
+
* that reads declarations does, and so does a declared consumer that cannot be loaded.
|
|
133
162
|
*/
|
|
134
163
|
function readProjects<Answer>(
|
|
135
164
|
engine: Engine,
|
|
@@ -137,10 +166,16 @@ function readProjects<Answer>(
|
|
|
137
166
|
scope: Scope,
|
|
138
167
|
config: Config,
|
|
139
168
|
stage: <Brand>(project: ProjectView<Brand>, read: ProjectRead) => Answer,
|
|
140
|
-
):
|
|
169
|
+
): ReadProjects<Answer> {
|
|
141
170
|
const targetRoot = scope.target.path;
|
|
142
171
|
const discovered = discoverProjects(engine, host, scope, config.analysis.configurations);
|
|
143
172
|
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
173
|
+
const loads = consumerLoads(engine, host, scope, discovered.configFiles);
|
|
174
|
+
const consumerOf = new Map(
|
|
175
|
+
loads.flatMap((load) =>
|
|
176
|
+
load.configFiles.map((configFile) => [configFile, load.consumer] as const),
|
|
177
|
+
),
|
|
178
|
+
);
|
|
144
179
|
const options = {
|
|
145
180
|
manifest: readManifest(host, targetRoot),
|
|
146
181
|
patterns: config.rootPatterns,
|
|
@@ -149,24 +184,51 @@ function readProjects<Answer>(
|
|
|
149
184
|
publishedAPI: config.targetKind === "library",
|
|
150
185
|
};
|
|
151
186
|
const failures: Diagnostic[] = [];
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
const consumed = new Map<string, Reference>();
|
|
188
|
+
const { projects } = runSession(
|
|
189
|
+
engine,
|
|
190
|
+
[...discovered.configFiles, ...consumerOf.keys()],
|
|
191
|
+
(project) => {
|
|
192
|
+
const errors = diagnosticErrors(diagnosticsOf(project));
|
|
193
|
+
if (errors.length > 0) {
|
|
194
|
+
failures.push(...errors);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
const consumer = consumerOf.get(project.configFile);
|
|
198
|
+
if (consumer !== undefined) {
|
|
199
|
+
for (const reference of consumerReferences(
|
|
200
|
+
project,
|
|
201
|
+
host,
|
|
202
|
+
targetRoot,
|
|
203
|
+
{
|
|
204
|
+
id: consumer.id,
|
|
205
|
+
root: consumer.path,
|
|
206
|
+
},
|
|
207
|
+
config,
|
|
208
|
+
)) {
|
|
209
|
+
consumed.set(referenceKey(reference), reference);
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
const held = inventory(project, host, targetRoot);
|
|
214
|
+
const rooted = roots(project, held, targetRoot, options);
|
|
215
|
+
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
216
|
+
return stage(project, { configuration, configFile: project.configFile, held, rooted });
|
|
217
|
+
},
|
|
218
|
+
);
|
|
163
219
|
if (failures.length > 0) {
|
|
164
220
|
throw new DiscoveryError(
|
|
165
221
|
`${String(failures.length)} error(s); no answer was produced`,
|
|
166
222
|
failures,
|
|
167
223
|
);
|
|
168
224
|
}
|
|
169
|
-
return
|
|
225
|
+
return {
|
|
226
|
+
answers: projects.filter((answer): answer is Answer => answer !== undefined),
|
|
227
|
+
consumers: {
|
|
228
|
+
loaded: loads.map((load) => load.consumer),
|
|
229
|
+
references: [...consumed.values()],
|
|
230
|
+
},
|
|
231
|
+
};
|
|
170
232
|
}
|
|
171
233
|
|
|
172
234
|
/**
|
|
@@ -183,7 +245,7 @@ export function runRoots(
|
|
|
183
245
|
provenance: Provenance,
|
|
184
246
|
): RunRoots {
|
|
185
247
|
const targetRoot = scope.target.path;
|
|
186
|
-
const projects = readProjects(engine, host, scope, config, (_project, read) => read);
|
|
248
|
+
const projects = readProjects(engine, host, scope, config, (_project, read) => read).answers;
|
|
187
249
|
|
|
188
250
|
const configurations: string[] = [];
|
|
189
251
|
const merged = new Map<string, Omit<RunRoot, "configurations"> & { configurations: string[] }>();
|
|
@@ -266,12 +328,15 @@ interface ReadRun<Extra> {
|
|
|
266
328
|
readonly matrix: Matrix;
|
|
267
329
|
readonly exempt: readonly Exemption[];
|
|
268
330
|
readonly projects: readonly SweptProject<Extra>[];
|
|
331
|
+
/** Every declared consumer, each loaded, in the scope's order. */
|
|
332
|
+
readonly consumers: readonly LoadedConsumer[];
|
|
269
333
|
}
|
|
270
334
|
|
|
271
335
|
/**
|
|
272
336
|
* Reads every project of the run, its references and exemptions, and `extra`'s answer,
|
|
273
337
|
* while the project's view is open, and merges the projects into one matrix in the run's
|
|
274
|
-
* order.
|
|
338
|
+
* order. Every configuration holds every consumer's references beside its own, because a
|
|
339
|
+
* consumer calls the target whichever configuration the target is built under.
|
|
275
340
|
*/
|
|
276
341
|
function readRun<Extra>(
|
|
277
342
|
engine: Engine,
|
|
@@ -288,7 +353,7 @@ function readRun<Extra>(
|
|
|
288
353
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
289
354
|
: readTemplates(host, targetRoot, config.analysis);
|
|
290
355
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
291
|
-
const
|
|
356
|
+
const read = readProjects(engine, host, scope, config, (project, projectRead) => {
|
|
292
357
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
293
358
|
testFiles: config.ts.testFiles,
|
|
294
359
|
});
|
|
@@ -315,10 +380,23 @@ function readRun<Extra>(
|
|
|
315
380
|
extra: extra(project, projectRead),
|
|
316
381
|
};
|
|
317
382
|
});
|
|
383
|
+
const consumed = read.consumers.references;
|
|
384
|
+
const projects = read.answers.map((one) =>
|
|
385
|
+
consumed.length === 0
|
|
386
|
+
? one
|
|
387
|
+
: {
|
|
388
|
+
...one,
|
|
389
|
+
configured: {
|
|
390
|
+
...one.configured,
|
|
391
|
+
references: [...one.configured.references, ...consumed],
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
);
|
|
318
395
|
return {
|
|
319
396
|
matrix: matrixOf(projects.map((one) => one.configured)),
|
|
320
397
|
exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
|
|
321
398
|
projects,
|
|
399
|
+
consumers: read.consumers.loaded,
|
|
322
400
|
};
|
|
323
401
|
}
|
|
324
402
|
|
|
@@ -430,7 +508,10 @@ function emitterInputOver(
|
|
|
430
508
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
431
509
|
},
|
|
432
510
|
boundary: {
|
|
433
|
-
consumers: {
|
|
511
|
+
consumers: {
|
|
512
|
+
declared: scope.consumers.map((consumer) => moduleIdentity(host, consumer)),
|
|
513
|
+
loaded: read.consumers.map((consumer) => consumer.id),
|
|
514
|
+
},
|
|
434
515
|
encapsulated: readManifest(host, targetRoot).declaresExports,
|
|
435
516
|
edges: readEdgeSides(host, targetRoot),
|
|
436
517
|
},
|
|
@@ -442,8 +523,8 @@ function emitterInputOver(
|
|
|
442
523
|
* What every emitter reads of the run the scope and the configuration describe, swept
|
|
443
524
|
* under the caller's marks: the run's sweep, the facts beside it each kind family reads,
|
|
444
525
|
* and the sides of the target's declared cross-language edges that name a symbol of this
|
|
445
|
-
* language. The run
|
|
446
|
-
*
|
|
526
|
+
* language. The run reads no suppression document, so the self-check family has nothing
|
|
527
|
+
* to report.
|
|
447
528
|
*/
|
|
448
529
|
export function runEmitterInput(
|
|
449
530
|
engine: Engine,
|
|
@@ -483,7 +564,11 @@ function runAnalysisInputs(
|
|
|
483
564
|
provenance: Provenance,
|
|
484
565
|
mode: Mode,
|
|
485
566
|
detectors: Detectors,
|
|
486
|
-
): {
|
|
567
|
+
): {
|
|
568
|
+
readonly inputs: AnalysisInputs;
|
|
569
|
+
readonly run: RunFacts;
|
|
570
|
+
readonly configured: readonly Configured[];
|
|
571
|
+
} {
|
|
487
572
|
const targetRoot = scope.target.path;
|
|
488
573
|
const read = readEmitterRun(engine, host, scope, config, mode, detectors);
|
|
489
574
|
const symbols = read.matrix.union.symbols;
|
|
@@ -522,7 +607,9 @@ function runAnalysisInputs(
|
|
|
522
607
|
config.ts.testFiles,
|
|
523
608
|
read.projects.flatMap((one) => one.configured.testFiles),
|
|
524
609
|
),
|
|
610
|
+
consumers: read.consumers,
|
|
525
611
|
},
|
|
612
|
+
configured: read.projects.map((one) => one.configured),
|
|
526
613
|
};
|
|
527
614
|
}
|
|
528
615
|
|
|
@@ -539,18 +626,25 @@ export interface RunFacts {
|
|
|
539
626
|
readonly projects: readonly RunProject[];
|
|
540
627
|
/** The rules that classified files as test files, each counted over every project at once. */
|
|
541
628
|
readonly testFileRules: readonly TestFileRule[];
|
|
629
|
+
/** Every consumer the run loaded beside the target, in the scope's order. */
|
|
630
|
+
readonly consumers: readonly LoadedConsumer[];
|
|
542
631
|
}
|
|
543
632
|
|
|
544
633
|
/** The findings of one run, beside what a report states about the run itself. */
|
|
545
634
|
export interface RunAnalysis {
|
|
546
635
|
readonly result: PassResult;
|
|
547
636
|
readonly run: RunFacts;
|
|
637
|
+
/** The sweep the findings were decided over, under the mark of every bound record. */
|
|
638
|
+
readonly swept: RunSweep;
|
|
639
|
+
/** What each project answered, in the run's order, which the sweep was built from. */
|
|
640
|
+
readonly configured: readonly Configured[];
|
|
548
641
|
}
|
|
549
642
|
|
|
550
643
|
/**
|
|
551
644
|
* The analysis of the run the scope and the configuration describe: the run read with its
|
|
552
645
|
* suppressions bound, every family's findings with the suppressions applied and the
|
|
553
|
-
* declared edges evaluated,
|
|
646
|
+
* declared edges evaluated, the projects and test-file rules the report names, and the sweep
|
|
647
|
+
* an explanation reads.
|
|
554
648
|
*/
|
|
555
649
|
export function runAnalysis(
|
|
556
650
|
engine: Engine,
|
|
@@ -561,7 +655,7 @@ export function runAnalysis(
|
|
|
561
655
|
mode: Mode,
|
|
562
656
|
detectors: Detectors = DETECTORS,
|
|
563
657
|
): RunAnalysis {
|
|
564
|
-
const { inputs, run } = runAnalysisInputs(
|
|
658
|
+
const { inputs, run, configured } = runAnalysisInputs(
|
|
565
659
|
engine,
|
|
566
660
|
host,
|
|
567
661
|
scope,
|
|
@@ -570,5 +664,5 @@ export function runAnalysis(
|
|
|
570
664
|
mode,
|
|
571
665
|
detectors,
|
|
572
666
|
);
|
|
573
|
-
return { result: findingsPass(inputs), run };
|
|
667
|
+
return { result: findingsPass(inputs), run, swept: inputs.marked.swept, configured };
|
|
574
668
|
}
|
package/src/config.ts
CHANGED
|
@@ -28,6 +28,14 @@ export type Language = "go" | "ts";
|
|
|
28
28
|
/** What a finding of one issue kind does to a run. */
|
|
29
29
|
export type Severity = "allow" | "warn" | "deny";
|
|
30
30
|
|
|
31
|
+
/** The severities ranked, the strongest highest. */
|
|
32
|
+
const SEVERITY_RANK: Readonly<Record<Severity, number>> = { allow: 0, warn: 1, deny: 2 };
|
|
33
|
+
|
|
34
|
+
/** Whether a finding at one severity fails a run under the failing severity. */
|
|
35
|
+
export function fails(severity: Severity, failOn: Severity): boolean {
|
|
36
|
+
return SEVERITY_RANK[severity] >= SEVERITY_RANK[failOn];
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
/** One rendering of the finding list. */
|
|
32
40
|
export type Format = "text" | "json" | "github" | "sarif" | "template";
|
|
33
41
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "1.9.0",
|
|
6
|
-
result: "
|
|
7
|
-
digest: "sha256:
|
|
6
|
+
result: "pass",
|
|
7
|
+
digest: "sha256:3e9e40876551faa2c196311bf06d47d220ab5127a9fb8921969aae552c42e824",
|
|
8
8
|
},
|
|
9
9
|
gaps: [
|
|
10
10
|
{
|
|
@@ -13,17 +13,5 @@ export const RECORD = {
|
|
|
13
13
|
reason:
|
|
14
14
|
"The reference pass resolves a string-literal element access to the member it names, so the member is live by that reference and the retained listing names no class for it.",
|
|
15
15
|
},
|
|
16
|
-
{
|
|
17
|
-
fixture: "redundant-export-keyword",
|
|
18
|
-
capability: "DS1104",
|
|
19
|
-
reason:
|
|
20
|
-
"No consumer is loaded beside the target, so the published surface is unknown and no export is narrowed; review an export only the target uses by hand.",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
fixture: "unused-exported-consumer",
|
|
24
|
-
capability: "DS1001",
|
|
25
|
-
reason:
|
|
26
|
-
"No consumer is loaded beside the target, so an export nothing in the target references is reported at the possible class rather than the certain one.",
|
|
27
|
-
},
|
|
28
16
|
],
|
|
29
17
|
} as const;
|
package/src/consumers.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consumers a scope declares, loaded beside the target: each a directory on the
|
|
3
|
+
* local filesystem whose compiler configurations open as projects of the run's one
|
|
4
|
+
* snapshot, and whose references to the target's declarations count against them.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Config } from "./config.ts";
|
|
8
|
+
import { consumerProjects } from "./discover.ts";
|
|
9
|
+
import type { Host } from "./host.ts";
|
|
10
|
+
import { inventory, packageScope } from "./inventory.ts";
|
|
11
|
+
import { relativePath } from "./paths.ts";
|
|
12
|
+
import { references, type ConsumerSide, type Reference } from "./references.ts";
|
|
13
|
+
import type { Module, Scope } from "./scope.ts";
|
|
14
|
+
import { narrowedTo, type Engine, type ProjectView } from "./session.ts";
|
|
15
|
+
|
|
16
|
+
/** One consumer the run loaded. */
|
|
17
|
+
export interface LoadedConsumer {
|
|
18
|
+
/** The name the consumer publishes itself under, which every finding names it by. */
|
|
19
|
+
readonly id: string;
|
|
20
|
+
/** The consumer's directory, absolute. */
|
|
21
|
+
readonly path: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** One declared consumer and the compiler configurations it opens as. */
|
|
25
|
+
interface ConsumerLoad {
|
|
26
|
+
readonly consumer: LoadedConsumer;
|
|
27
|
+
/** The configuration files, absolute, in discovery order, none a target project's. */
|
|
28
|
+
readonly configFiles: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The name one module publishes itself under: the scope's, else its manifest's. */
|
|
32
|
+
export function moduleIdentity(host: Host, module: Module): string {
|
|
33
|
+
return module.id === "" ? packageScope(host, module.path)("index.ts").package : module.id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Every consumer the scope declares, in its order, with the projects it opens as. A
|
|
38
|
+
* configuration that is also one of the target's projects, or an earlier consumer's, is
|
|
39
|
+
* that project's alone. A consumer whose directory is absent, is no directory or holds
|
|
40
|
+
* no configuration ends the run naming it, before any project opens.
|
|
41
|
+
*/
|
|
42
|
+
export function consumerLoads(
|
|
43
|
+
engine: Engine,
|
|
44
|
+
host: Host,
|
|
45
|
+
scope: Scope,
|
|
46
|
+
targetConfigFiles: readonly string[],
|
|
47
|
+
): readonly ConsumerLoad[] {
|
|
48
|
+
const opened = new Set(targetConfigFiles);
|
|
49
|
+
return scope.consumers.map((module) => {
|
|
50
|
+
const configFiles = consumerProjects(engine, host, module.path).filter((configFile) => {
|
|
51
|
+
const fresh = !opened.has(configFile);
|
|
52
|
+
opened.add(configFile);
|
|
53
|
+
return fresh;
|
|
54
|
+
});
|
|
55
|
+
return { consumer: { id: moduleIdentity(host, module), path: module.path }, configFiles };
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Every reference one consumer project's own files make to a declaration of the target.
|
|
61
|
+
* The target's declarations are enumerated over the target's files the consumer's
|
|
62
|
+
* program holds, against the target root, so each carries the identifier the target's
|
|
63
|
+
* own projects give it. A reference from a consumer's test file is a test reference
|
|
64
|
+
* unless the configuration counts it as a production one.
|
|
65
|
+
*/
|
|
66
|
+
export function consumerReferences<Brand>(
|
|
67
|
+
project: ProjectView<Brand>,
|
|
68
|
+
host: Host,
|
|
69
|
+
targetRoot: string,
|
|
70
|
+
consumer: ConsumerSide,
|
|
71
|
+
config: Config,
|
|
72
|
+
): readonly Reference[] {
|
|
73
|
+
const held = inventory(
|
|
74
|
+
narrowedTo(project, (file) => relativePath(targetRoot, file.fileName) !== undefined),
|
|
75
|
+
host,
|
|
76
|
+
targetRoot,
|
|
77
|
+
);
|
|
78
|
+
const found = references(project, held, targetRoot, {
|
|
79
|
+
testFiles: config.ts.testFiles,
|
|
80
|
+
consumer,
|
|
81
|
+
}).references;
|
|
82
|
+
if (config.analysis.consumerTests !== "production") {
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
return found.map((reference) => (reference.test ? { ...reference, test: false } : reference));
|
|
86
|
+
}
|
package/src/discover.ts
CHANGED
|
@@ -160,17 +160,70 @@ function referencesOf(host: Host, configFile: string): string[] {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
* The configuration
|
|
164
|
-
*
|
|
165
|
-
*
|
|
163
|
+
* The configuration file the scope names for the target directly, where its path
|
|
164
|
+
* names one rather than a directory. A target naming a directory is reached by the
|
|
165
|
+
* walk over that directory instead. A consumer's projects are a consumer's, never the
|
|
166
|
+
* target's, so no consumer path is read here.
|
|
166
167
|
*/
|
|
167
168
|
function scopeConfigFiles(host: Host, scope: Scope): string[] {
|
|
168
|
-
return [scope.target
|
|
169
|
-
.map((module) => module.path)
|
|
169
|
+
return [scope.target.path]
|
|
170
170
|
.filter((path) => path.endsWith(".json"))
|
|
171
171
|
.map((path) => resolvePath(host.workingDirectory(), path));
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Every configuration file reached from `pending`, in the order discovery reads them:
|
|
176
|
+
* each once, each followed by the configurations it references, transitively. A
|
|
177
|
+
* configuration the compiler cannot read ends the run naming it.
|
|
178
|
+
*/
|
|
179
|
+
function derivedFrom(engine: Engine, host: Host, pending: string[]): string[] {
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
const found: string[] = [];
|
|
182
|
+
while (pending.length > 0) {
|
|
183
|
+
const configFile = pending.shift();
|
|
184
|
+
if (configFile === undefined) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
const path = resolvePath(host.workingDirectory(), configFile);
|
|
188
|
+
if (seen.has(path)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
seen.add(path);
|
|
192
|
+
parseOrRefuse(engine, path, path);
|
|
193
|
+
found.push(path);
|
|
194
|
+
pending.push(...referencesOf(host, path));
|
|
195
|
+
}
|
|
196
|
+
return found;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The compiler configurations one consumer is loaded through: every one under its
|
|
201
|
+
* root that is not inside an ignored directory, and the configurations each of those
|
|
202
|
+
* references, transitively, in that order. A root that is absent, that is no
|
|
203
|
+
* directory, or that holds no configuration ends the run naming it, because a
|
|
204
|
+
* finding computed without a declared consumer would claim a reference set the run
|
|
205
|
+
* never read.
|
|
206
|
+
*/
|
|
207
|
+
export function consumerProjects(engine: Engine, host: Host, root: string): readonly string[] {
|
|
208
|
+
const kind = host.kindOf(root);
|
|
209
|
+
if (kind !== "directory") {
|
|
210
|
+
throw new DiscoveryError(
|
|
211
|
+
kind === "absent"
|
|
212
|
+
? `the consumer ${root} does not exist`
|
|
213
|
+
: `the consumer ${root} is not a directory`,
|
|
214
|
+
[],
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const found = derivedFrom(engine, host, configFilesUnder(host, root));
|
|
218
|
+
if (found.length === 0) {
|
|
219
|
+
throw new DiscoveryError(
|
|
220
|
+
`the consumer ${root} holds no compiler configuration, so none of its references can be read`,
|
|
221
|
+
[],
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return found;
|
|
225
|
+
}
|
|
226
|
+
|
|
174
227
|
/**
|
|
175
228
|
* The name a derived project carries: its configuration file's path below the target
|
|
176
229
|
* root, or the absolute path of a file outside it.
|
|
@@ -257,24 +310,15 @@ function declaredProjects(
|
|
|
257
310
|
*/
|
|
258
311
|
function derivedProjects(engine: Engine, host: Host, scope: Scope): Discovery {
|
|
259
312
|
const fromScope = scopeConfigFiles(host, scope);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (seen.has(path)) {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
seen.add(path);
|
|
273
|
-
parseOrRefuse(engine, path, path);
|
|
274
|
-
projects.push({ id: derivedId(scope.target.path, path), configFile: path });
|
|
275
|
-
pending.push(...referencesOf(host, path));
|
|
276
|
-
}
|
|
277
|
-
return { projects, configFiles: projects.map((project) => project.configFile), fromScope };
|
|
313
|
+
const configFiles = derivedFrom(engine, host, [
|
|
314
|
+
...fromScope,
|
|
315
|
+
...configFilesUnder(host, scope.target.path),
|
|
316
|
+
]);
|
|
317
|
+
const projects = configFiles.map((configFile) => ({
|
|
318
|
+
id: derivedId(scope.target.path, configFile),
|
|
319
|
+
configFile,
|
|
320
|
+
}));
|
|
321
|
+
return { projects, configFiles, fromScope };
|
|
278
322
|
}
|
|
279
323
|
|
|
280
324
|
/**
|