@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analysis.js +30 -6
- package/dist/src/augmentations.js +105 -12
- package/dist/src/build-scripts.js +437 -0
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/convention-rows.js +62 -2
- package/dist/src/conventions.js +15 -0
- package/dist/src/dependencies.js +89 -0
- package/dist/src/entry-points.js +9 -18
- package/dist/src/findings/completion.js +122 -49
- package/dist/src/findings/intra-function.js +4 -2
- package/dist/src/findings/reads-and-writes.js +7 -6
- package/dist/src/findings/unused-declarations.js +13 -5
- package/dist/src/findings/visibility-narrowing.js +8 -3
- package/dist/src/graph.js +5 -3
- package/dist/src/intra-function-parts.js +45 -17
- package/dist/src/kinds.js +8 -8
- package/dist/src/manifest.js +18 -0
- package/dist/src/matrix.js +76 -4
- package/dist/src/references.js +34 -3
- package/dist/src/report.js +23 -4
- package/dist/src/reporters/sarif.js +12 -8
- package/dist/src/roots.js +138 -11
- package/dist/src/serialization-contract.js +258 -8
- package/dist/src/suppress.js +1 -0
- package/dist/src/sweep.js +7 -9
- package/dist/src/test-globals.js +234 -0
- package/dist/src/test-support.js +8 -11
- package/dist/src/type-errors.js +11 -3
- package/dist/src/unseen-loads.js +107 -0
- package/dist/src/verbs/analyze.js +24 -2
- package/dist/src/yaml-document.js +355 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +48 -8
- package/src/augmentations.ts +134 -16
- package/src/build-scripts.ts +564 -0
- package/src/conformance-record.ts +1 -1
- package/src/convention-rows.ts +72 -2
- package/src/conventions.ts +16 -0
- package/src/dependencies.ts +96 -0
- package/src/entry-points.ts +10 -25
- package/src/file-facts.ts +3 -0
- package/src/finding.ts +5 -0
- package/src/findings/completion.ts +153 -56
- package/src/findings/intra-function.ts +4 -2
- package/src/findings/reads-and-writes.ts +7 -6
- package/src/findings/unused-declarations.ts +17 -5
- package/src/findings/visibility-narrowing.ts +9 -3
- package/src/graph.ts +5 -3
- package/src/intra-function-parts.ts +55 -16
- package/src/kinds.ts +8 -8
- package/src/manifest.ts +21 -3
- package/src/matrix.ts +122 -4
- package/src/references.ts +38 -2
- package/src/report.ts +56 -16
- package/src/reporters/sarif.ts +16 -8
- package/src/roots.ts +162 -12
- package/src/serialization-contract.ts +309 -7
- package/src/suppress.ts +1 -0
- package/src/sweep.ts +21 -9
- package/src/test-globals.ts +275 -0
- package/src/test-support.ts +17 -13
- package/src/type-errors.ts +14 -3
- package/src/unseen-loads.ts +145 -0
- package/src/verbs/analyze.ts +28 -2
- package/src/yaml-document.ts +377 -0
package/dist/src/analysis.js
CHANGED
|
@@ -43,7 +43,10 @@ import { references, testFileRulesOf } from "./references.js";
|
|
|
43
43
|
import { roots, unmatchedEverywhere, } from "./roots.js";
|
|
44
44
|
import { serializationContract } from "./serialization-contract.js";
|
|
45
45
|
import { outsideTestReferences, testFilesAt } from "./outside-tests.js";
|
|
46
|
+
import { mergedGlobalInterfaces } from "./augmentations.js";
|
|
47
|
+
import { unseenLoadsOf } from "./unseen-loads.js";
|
|
46
48
|
import { joinedTestFiles, withJoinedTests } from "./test-joins.js";
|
|
49
|
+
import { GLOBALS_RUNNER, namesTestGlobals, withTestGlobals } from "./test-globals.js";
|
|
47
50
|
import { supportReferences, testSupportFiles } from "./test-support.js";
|
|
48
51
|
import { diagnosticsOf, ownedAs, runSession, } from "./session.js";
|
|
49
52
|
import { accessorsOf, storesOf } from "./stores.js";
|
|
@@ -195,7 +198,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
195
198
|
const joins = discovered.derived
|
|
196
199
|
? joinedTestFiles(engine, host, absoluteRoot, discovered.configFiles, testPatterns)
|
|
197
200
|
: new Map();
|
|
198
|
-
const programs = withBaseUrlPaths(engine, host, [...discovered.configFiles, ...consumerOf.keys()], workspace?.programs ?? new Map());
|
|
201
|
+
const programs = withTestGlobals(engine, host, parse, discovered.configFiles, withBaseUrlPaths(engine, host, [...discovered.configFiles, ...consumerOf.keys()], workspace?.programs ?? new Map()));
|
|
199
202
|
const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (opened) => {
|
|
200
203
|
const consumer = consumerOf.get(opened.configFile);
|
|
201
204
|
const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
|
|
@@ -499,6 +502,9 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
499
502
|
: { generated: projectRead.generated }),
|
|
500
503
|
}, detectors, disabled);
|
|
501
504
|
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
505
|
+
const injected = project.program
|
|
506
|
+
.getSourceFileNames()
|
|
507
|
+
.some((name) => name.endsWith(`/node_modules/${GLOBALS_RUNNER}/globals.d.ts`));
|
|
502
508
|
const pathOf = (file) => relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
503
509
|
return {
|
|
504
510
|
projectRead,
|
|
@@ -506,11 +512,22 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
506
512
|
testFiles: resolved.testFilePaths,
|
|
507
513
|
evidence,
|
|
508
514
|
componentFiles: components.map(pathOf),
|
|
515
|
+
augmented: mergedGlobalInterfaces(project, projectRead.held, sourceFilesOf(project, targetRoot)).map((id) => ({ id, kind: "global-augmentation", source: "" })),
|
|
509
516
|
imports: new Map(project.ownSourceFiles().flatMap((file) => {
|
|
510
517
|
const path = relativePath(targetRoot, file.fileName);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
518
|
+
if (path === undefined) {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
const specifiers = file.imports.filter(isStringLiteralLikeNode).map((one) => one.text);
|
|
522
|
+
// A file calling the globals a program declares is written for their runner.
|
|
523
|
+
return [
|
|
524
|
+
[
|
|
525
|
+
path,
|
|
526
|
+
injected && namesTestGlobals(project, file)
|
|
527
|
+
? [...specifiers, GLOBALS_RUNNER]
|
|
528
|
+
: specifiers,
|
|
529
|
+
],
|
|
530
|
+
];
|
|
514
531
|
})),
|
|
515
532
|
componentWarnings: components.flatMap((file) => readComponent(file.originalText, extensionOf(file.fileName)).warnings.map((warning) => ({
|
|
516
533
|
path: pathOf(file),
|
|
@@ -531,7 +548,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
531
548
|
}));
|
|
532
549
|
// Test-support code is code only test code imports in any configuration of the run: a
|
|
533
550
|
// configuration that holds no test file says nothing about what the tests import.
|
|
534
|
-
const support = testSupportFiles(withOutside.flatMap((one) => one.projectRead.held.symbols), withOutside.flatMap((one) => one.references), withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability), withOutside.flatMap((one) => one.testFiles), testEvidence(host, targetRoot, withOutside));
|
|
551
|
+
const { support, testUsed } = testSupportFiles(withOutside.flatMap((one) => one.projectRead.held.symbols), withOutside.flatMap((one) => one.references), withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability), withOutside.flatMap((one) => one.testFiles), testEvidence(host, targetRoot, withOutside));
|
|
535
552
|
const settled = settledSatisfaction(withOutside.map((one) => ({ evidence: one.evidence, held: one.projectRead.held })), [...withOutside.flatMap((one) => one.references), ...read.consumers.references]);
|
|
536
553
|
const answers = withOutside.map((one, index) => {
|
|
537
554
|
const { projectRead } = one;
|
|
@@ -544,13 +561,17 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
544
561
|
const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
|
|
545
562
|
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
546
563
|
const supportFiles = [...support].filter((path) => held.has(path)).sort();
|
|
564
|
+
const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort();
|
|
547
565
|
const configured = {
|
|
548
566
|
configuration: projectRead.configuration,
|
|
549
567
|
symbols: projectRead.held.symbols,
|
|
550
568
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
551
|
-
|
|
569
|
+
// The augmentations are rooted after test support is read: an outside declaration
|
|
570
|
+
// naming one calls nothing of its file.
|
|
571
|
+
roots: [...projectRead.rooted.liveUnderReachability, ...one.augmented],
|
|
552
572
|
testFiles: [...one.testFiles, ...supportFiles],
|
|
553
573
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
574
|
+
...(testUsedFiles.length === 0 ? {} : { testUsedFiles }),
|
|
554
575
|
...(projectRead.held.ambient.size === 0 ? {} : { ambient: projectRead.held.ambient }),
|
|
555
576
|
...(projectRead.referenceOnly.length === 0
|
|
556
577
|
? {}
|
|
@@ -759,6 +780,7 @@ export function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
|
759
780
|
directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
|
|
760
781
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
761
782
|
parts: projectParts(project, read.held, references, targetRoot),
|
|
783
|
+
unseenLoads: unseenLoadsOf(project, read.configuration, targetRoot, testFiles),
|
|
762
784
|
}));
|
|
763
785
|
// Whether a declaration carries the marker decides its code, so one whose marker went
|
|
764
786
|
// unread is reported by nothing.
|
|
@@ -834,6 +856,7 @@ export function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
834
856
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
835
857
|
generated: new Set(projects.flatMap((one) => one.extra.generated)),
|
|
836
858
|
inOperands: new Set(projects.flatMap((one) => one.extra.inOperands)),
|
|
859
|
+
unseenLoads: projects.flatMap((one) => one.extra.unseenLoads),
|
|
837
860
|
},
|
|
838
861
|
boundary: {
|
|
839
862
|
consumers: {
|
|
@@ -884,6 +907,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
|
|
|
884
907
|
librariesUnfound: read.librariesUnfound,
|
|
885
908
|
conventionsApplied: read.conventionsApplied,
|
|
886
909
|
unmappedEntries: unmappedEverywhere(read.projects),
|
|
910
|
+
unseenLoads: read.projects.flatMap((one) => one.extra.unseenLoads),
|
|
887
911
|
},
|
|
888
912
|
configured: read.projects.map((one) => one.configured),
|
|
889
913
|
symbols,
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* name, which the language or a library types values by where the target never names it,
|
|
7
7
|
* as `import.meta` or `process.env`, so a use of its member is a use of it.
|
|
8
8
|
*/
|
|
9
|
+
import { isInterfaceDeclaration, isModuleBlock, isModuleDeclaration, } from "@typescript/native/ast";
|
|
10
|
+
import { nodeKey } from "./inventory.js";
|
|
9
11
|
import { referenceKey } from "./matrix.js";
|
|
10
12
|
/** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
|
|
11
13
|
function isGlobalBlock(symbol, held, files) {
|
|
@@ -15,45 +17,136 @@ function isGlobalBlock(symbol, held, files) {
|
|
|
15
17
|
!held.modules.has(symbol.id));
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
|
-
*
|
|
19
|
-
* file in `scripts
|
|
20
|
-
*
|
|
21
|
-
* files that are no module, by identifier.
|
|
20
|
+
* Per declaration, the interfaces and namespaces between it and the augmentation block or
|
|
21
|
+
* the file in `scripts` that holds it, with the block itself, or none where no such block
|
|
22
|
+
* or file holds it; and whether that holder is the global scope rather than a module
|
|
23
|
+
* declaration. `scripts` holds the files that are no module, by identifier.
|
|
22
24
|
*/
|
|
23
|
-
|
|
25
|
+
function holdersOf(held, scripts) {
|
|
24
26
|
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
25
27
|
const globals = new Set(held.symbols.filter((one) => isGlobalBlock(one, held, files)).map((one) => one.id));
|
|
26
28
|
if (globals.size === 0 && held.modules.size === 0 && scripts.size === 0) {
|
|
27
|
-
return
|
|
29
|
+
return undefined;
|
|
28
30
|
}
|
|
29
31
|
const byId = new Map(held.symbols.map((one) => [one.id, one]));
|
|
30
|
-
|
|
32
|
+
return (id) => {
|
|
31
33
|
const ids = [];
|
|
32
34
|
for (let at = byId.get(byId.get(id)?.parent ?? ""); at !== undefined; at = byId.get(at.parent)) {
|
|
33
35
|
if (globals.has(at.id) || held.modules.has(at.id)) {
|
|
34
|
-
return [...ids, at.id];
|
|
36
|
+
return { ids: [...ids, at.id], global: globals.has(at.id) };
|
|
35
37
|
}
|
|
36
38
|
if (scripts.has(at.id)) {
|
|
37
|
-
return ids;
|
|
39
|
+
return { ids, global: true };
|
|
38
40
|
}
|
|
39
41
|
if (at.kind === "interface" || at.kind === "namespace") {
|
|
40
42
|
ids.push(at.id);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
return { ids: [], global: false };
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The ambient interfaces of the global scope among `interfaces`: each one a declaration
|
|
50
|
+
* file or a `declare` holds that a `declare global` block, or the top level of a file in
|
|
51
|
+
* `scripts`, declares, directly or inside a namespace there.
|
|
52
|
+
*/
|
|
53
|
+
export function ambientGlobalInterfaces(held, interfaces, scripts) {
|
|
54
|
+
const holders = holdersOf(held, scripts);
|
|
55
|
+
return holders === undefined
|
|
56
|
+
? []
|
|
57
|
+
: interfaces.filter((id) => held.ambient.has(id) && holders(id).global);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Each ambient global interface of the project's own files that merges into an interface
|
|
61
|
+
* declared outside them, with every interface, namespace and block between it and the
|
|
62
|
+
* global scope: the outside declaration names it wherever a value is typed by it. Where
|
|
63
|
+
* every outside declaration is a package's, not the compiler's library, and declares no
|
|
64
|
+
* member, each member it declares as well: such an interface is a slot the package reads
|
|
65
|
+
* by type, as `keyof` does, in declarations the analysis does not walk.
|
|
66
|
+
*/
|
|
67
|
+
export function mergedGlobalInterfaces(project, held, files) {
|
|
68
|
+
const scripts = new Set();
|
|
69
|
+
const names = new Map();
|
|
70
|
+
for (const file of files.byName.values()) {
|
|
71
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
72
|
+
if (fileId !== undefined && file.externalModuleIndicator === undefined) {
|
|
73
|
+
scripts.add(fileId);
|
|
74
|
+
}
|
|
75
|
+
const visit = (node) => {
|
|
76
|
+
if (isInterfaceDeclaration(node)) {
|
|
77
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
78
|
+
if (id !== undefined) {
|
|
79
|
+
names.set(id, node.name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (isModuleDeclaration(node) || isModuleBlock(node)) {
|
|
83
|
+
node.forEachChild(visit);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
file.forEachChild(visit);
|
|
87
|
+
}
|
|
88
|
+
const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
|
|
89
|
+
const asked = [...names].filter(([id]) => global.has(id));
|
|
90
|
+
if (asked.length === 0) {
|
|
43
91
|
return [];
|
|
92
|
+
}
|
|
93
|
+
const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
|
|
94
|
+
const holders = holdersOf(held, scripts);
|
|
95
|
+
const empty = (handle) => {
|
|
96
|
+
if (project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const node = project.declarationAt(handle)?.node;
|
|
100
|
+
return node !== undefined && isInterfaceDeclaration(node) && node.members.length === 0;
|
|
44
101
|
};
|
|
102
|
+
return [
|
|
103
|
+
...new Set(asked.flatMap(([id], at) => {
|
|
104
|
+
const symbol = answers[at];
|
|
105
|
+
if (typeof symbol !== "object") {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const outside = symbol.declarations.filter((handle) => !files.byName.has(handle.path));
|
|
109
|
+
if (outside.length === 0) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const members = outside.every(empty)
|
|
113
|
+
? held.symbols.filter((one) => one.parent === id).map((one) => one.id)
|
|
114
|
+
: [];
|
|
115
|
+
return [id, ...(holders?.(id).ids ?? []), ...members];
|
|
116
|
+
})),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* For each reference to a declaration an augmentation block, or the global scope of a
|
|
121
|
+
* file in `scripts` (the files that are no module), holds, a read at the same place of
|
|
122
|
+
* every interface and namespace between it and the block or the file, and of the block
|
|
123
|
+
* itself. A read through an interface of `merged`, an ambient global interface merging
|
|
124
|
+
* into a language or library one, is a production read whoever writes it: it types a
|
|
125
|
+
* value the runtime provides, so a test reading a member needs it as code does.
|
|
126
|
+
*/
|
|
127
|
+
export function augmentationReferences(held, found, scripts = new Set(), merged = new Set()) {
|
|
128
|
+
const holders = holdersOf(held, scripts);
|
|
129
|
+
if (holders === undefined) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const fileOf = new Map(held.symbols.filter((one) => one.kind === "file").map((one) => [one.position.path, one.id]));
|
|
45
133
|
// Two uses at one place of declarations one block holds read the block once there.
|
|
46
134
|
const made = new Set(found.map(referenceKey));
|
|
47
135
|
const reads = new Map();
|
|
48
136
|
for (const reference of found) {
|
|
49
|
-
|
|
137
|
+
const { ids } = holders(reference.to);
|
|
138
|
+
// A test's read through such an interface is held by the test file, which the test
|
|
139
|
+
// runner loads whichever of its declarations runs.
|
|
140
|
+
const typed = reference.test && ids.some((id) => merged.has(id));
|
|
141
|
+
const from = typed ? (fileOf.get(reference.position.path) ?? reference.from) : reference.from;
|
|
142
|
+
for (const to of ids) {
|
|
50
143
|
const read = {
|
|
51
|
-
from
|
|
144
|
+
from,
|
|
52
145
|
to,
|
|
53
146
|
position: reference.position,
|
|
54
147
|
use: "read",
|
|
55
148
|
resolution: reference.resolution,
|
|
56
|
-
test: reference.test,
|
|
149
|
+
test: reference.test && !typed,
|
|
57
150
|
};
|
|
58
151
|
const key = referenceKey(read);
|
|
59
152
|
if (!made.has(key) && !reads.has(key)) {
|