@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.4
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 +1 -1
- package/dist/src/analysis.js +61 -10
- package/dist/src/augmentations.js +63 -1
- package/dist/src/build-scripts.js +437 -0
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/convention-rows.js +39 -2
- package/dist/src/conventions.js +15 -0
- package/dist/src/dependencies.js +98 -1
- package/dist/src/discover.js +14 -82
- package/dist/src/entry-points.js +9 -18
- package/dist/src/exempt.js +8 -0
- package/dist/src/findings/completion.js +66 -28
- 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 +4 -1
- 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 +38 -4
- package/dist/src/query.js +1 -0
- package/dist/src/report.js +23 -4
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +12 -8
- package/dist/src/roots.js +197 -42
- package/dist/src/serialization-contract.js +590 -12
- package/dist/src/sweep.js +7 -9
- package/dist/src/test-globals.js +234 -0
- package/dist/src/type-errors.js +11 -3
- package/dist/src/unseen-loads.js +107 -0
- package/dist/src/verbs/analyze.js +24 -3
- package/dist/src/workflow-steps.js +32 -15
- package/dist/src/yaml-document.js +355 -0
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +109 -12
- package/src/augmentations.ts +77 -2
- package/src/build-scripts.ts +564 -0
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +64 -12
- package/src/convention-rows.ts +47 -2
- package/src/conventions.ts +16 -0
- package/src/dependencies.ts +110 -1
- package/src/discover.ts +14 -94
- package/src/entry-points.ts +10 -25
- package/src/exempt.ts +16 -0
- package/src/file-facts.ts +3 -0
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +76 -30
- package/src/findings/intra-function.ts +4 -2
- package/src/findings/reads-and-writes.ts +7 -6
- package/src/findings/unused-declarations.ts +6 -1
- 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 +51 -4
- package/src/query.ts +11 -0
- package/src/report.ts +56 -16
- package/src/reporters/annotations.ts +21 -10
- package/src/reporters/reporter.ts +1 -3
- package/src/reporters/sarif.ts +16 -8
- package/src/roots.ts +226 -46
- package/src/serialization-contract.ts +758 -12
- package/src/sweep.ts +23 -9
- package/src/test-globals.ts +275 -0
- package/src/type-errors.ts +14 -3
- package/src/unseen-loads.ts +145 -0
- package/src/verbs/analyze.ts +28 -3
- package/src/workflow-steps.ts +35 -17
- package/src/yaml-document.ts +377 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
deadset-ts finds dead code in TypeScript projects, down to class members, type members and dead stores inside functions, and writes a report your CI can fail on.
|
|
6
6
|
|
|
7
|
-
It reads your `tsconfig.json` projects, `allowJs` JavaScript and Vue, Svelte and Astro scripts included, with the type information of TypeScript 7.1.0-dev.
|
|
7
|
+
It reads your `tsconfig.json` projects, `allowJs` JavaScript and Vue, Svelte and Astro scripts included, with the type information of TypeScript 7.1.0-dev.20261014.1, its one dependency. Your project keeps its own TypeScript version. A type error under that version skips the function or statement that holds it. deadset-ts reports and never edits your code. It is pre-release, tested only on Linux, needs Node.js 24 or later and is licensed under GPL-3.0-or-later.
|
|
8
8
|
|
|
9
9
|
## Why use it
|
|
10
10
|
|
package/dist/src/analysis.js
CHANGED
|
@@ -20,7 +20,7 @@ import { enumGroup } from "./enum-group.js";
|
|
|
20
20
|
import { generatedFile, generatedFilesOf } from "./generated-file.js";
|
|
21
21
|
import { inOperandNames } from "./in-operands.js";
|
|
22
22
|
import { sourceFilesOf } from "./source-files.js";
|
|
23
|
-
import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
|
|
23
|
+
import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, writtenRecords, } from "./exempt.js";
|
|
24
24
|
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
|
|
25
25
|
import { globExpression } from "./glob.js";
|
|
26
26
|
import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
|
|
@@ -41,9 +41,12 @@ import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.
|
|
|
41
41
|
import { reflectiveLookup } from "./reflective-lookup.js";
|
|
42
42
|
import { references, testFileRulesOf } from "./references.js";
|
|
43
43
|
import { roots, unmatchedEverywhere, } from "./roots.js";
|
|
44
|
-
import { serializationContract } from "./serialization-contract.js";
|
|
44
|
+
import { decodedMembers, 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,17 +512,29 @@ 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),
|
|
517
534
|
line: lineOf(file.originalText, warning.offset),
|
|
518
535
|
reason: warning.reason,
|
|
519
536
|
}))),
|
|
537
|
+
decoded: decodedMembers(project, projectRead.held, productionFiles(project, targetRoot, resolved.testFilePaths), consumers),
|
|
520
538
|
extra: extra(project, projectRead, resolved.references, resolved.testFilePaths),
|
|
521
539
|
};
|
|
522
540
|
});
|
|
@@ -532,11 +550,24 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
532
550
|
// Test-support code is code only test code imports in any configuration of the run: a
|
|
533
551
|
// configuration that holds no test file says nothing about what the tests import.
|
|
534
552
|
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
|
-
|
|
553
|
+
// A member a decode fills or a copy carries is written: a value decoded and sent again carries it on.
|
|
554
|
+
const written = new Set([
|
|
555
|
+
...countedWrites([...withOutside.flatMap((one) => one.references), ...read.consumers.references], support, mode),
|
|
556
|
+
...withOutside.flatMap((one) => [
|
|
557
|
+
...one.decoded.filled,
|
|
558
|
+
...one.decoded.parsed,
|
|
559
|
+
...one.decoded.copied,
|
|
560
|
+
]),
|
|
561
|
+
]);
|
|
562
|
+
const evidence = withOutside.map((one) => writtenRecords(one.evidence, written));
|
|
563
|
+
const settled = settledSatisfaction(withOutside.map((one, index) => ({
|
|
564
|
+
evidence: evidence[index] ?? [],
|
|
565
|
+
held: one.projectRead.held,
|
|
566
|
+
})), [...withOutside.flatMap((one) => one.references), ...read.consumers.references]);
|
|
536
567
|
const answers = withOutside.map((one, index) => {
|
|
537
568
|
const { projectRead } = one;
|
|
538
569
|
const satisfied = settled[index];
|
|
539
|
-
const detected = heldRecords(satisfied?.evidence ??
|
|
570
|
+
const detected = heldRecords(satisfied?.evidence ?? evidence[index] ?? [], {
|
|
540
571
|
mode,
|
|
541
572
|
testFiles: new Set([...one.testFiles, ...support]),
|
|
542
573
|
});
|
|
@@ -549,7 +580,9 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
549
580
|
configuration: projectRead.configuration,
|
|
550
581
|
symbols: projectRead.held.symbols,
|
|
551
582
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
552
|
-
|
|
583
|
+
// The augmentations are rooted after test support is read: an outside declaration
|
|
584
|
+
// naming one calls nothing of its file.
|
|
585
|
+
roots: [...projectRead.rooted.liveUnderReachability, ...one.augmented],
|
|
553
586
|
testFiles: [...one.testFiles, ...supportFiles],
|
|
554
587
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
555
588
|
...(testUsedFiles.length === 0 ? {} : { testUsedFiles }),
|
|
@@ -572,7 +605,9 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
572
605
|
.filter((reference) => reference.resolution === "by-name")
|
|
573
606
|
.map((reference) => reference.to),
|
|
574
607
|
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
608
|
+
...one.decoded.unanswered,
|
|
575
609
|
],
|
|
610
|
+
filledOutside: one.decoded.filled,
|
|
576
611
|
typeErrorSkips: projectRead.typeErrorSkips,
|
|
577
612
|
unmappedEntries: projectRead.rooted.unmappedEntries,
|
|
578
613
|
skipped: projectRead.skipped,
|
|
@@ -662,6 +697,13 @@ function evidenceReferences(symbols, inTestFiles) {
|
|
|
662
697
|
];
|
|
663
698
|
});
|
|
664
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* The declarations a write the mode counts stores into, a write a test-support file
|
|
702
|
+
* makes being a test's.
|
|
703
|
+
*/
|
|
704
|
+
function countedWrites(references, support, mode) {
|
|
705
|
+
return new Set(supportReferences(references, support).flatMap((reference) => reference.use === "write" && !(mode.production && reference.test) ? [reference.to] : []));
|
|
706
|
+
}
|
|
665
707
|
/**
|
|
666
708
|
* The declarations every reference the mode counts stores into, a loaded consumer's
|
|
667
709
|
* reference counting as a read: what the write-only kind would report but for a record.
|
|
@@ -746,6 +788,11 @@ function sweptOf(read, input) {
|
|
|
746
788
|
export function runSweep(engine, host, scope, config, input, detectors = DETECTORS) {
|
|
747
789
|
return sweptOf(readRun(engine, host, scope, config, input.mode, detectors, () => undefined), input);
|
|
748
790
|
}
|
|
791
|
+
/** The project's own files below the target root that are no test file. */
|
|
792
|
+
function productionFiles(project, targetRoot, testFiles) {
|
|
793
|
+
const tests = new Set(testFiles);
|
|
794
|
+
return [...sourceFilesOf(project, targetRoot).byPath].flatMap(([path, file]) => tests.has(path) ? [] : [file]);
|
|
795
|
+
}
|
|
749
796
|
/** Reads a run's projects with every per-project fact an emitter reads. */
|
|
750
797
|
export function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
751
798
|
const targetRoot = scope.target.path;
|
|
@@ -761,8 +808,9 @@ export function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
|
761
808
|
directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
|
|
762
809
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
763
810
|
parts: projectParts(project, read.held, references, targetRoot),
|
|
811
|
+
unseenLoads: unseenLoadsOf(project, read.configuration, targetRoot, testFiles),
|
|
764
812
|
}));
|
|
765
|
-
// Whether a declaration carries the marker decides its code, so one whose
|
|
813
|
+
// Whether a declaration carries the marker decides its code, so one whose answer went
|
|
766
814
|
// unread is reported by nothing.
|
|
767
815
|
const unread = run.projects.flatMap((one) => one.extra.deprecation.unanswered);
|
|
768
816
|
return unread.length === 0
|
|
@@ -836,6 +884,7 @@ export function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
836
884
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
837
885
|
generated: new Set(projects.flatMap((one) => one.extra.generated)),
|
|
838
886
|
inOperands: new Set(projects.flatMap((one) => one.extra.inOperands)),
|
|
887
|
+
unseenLoads: projects.flatMap((one) => one.extra.unseenLoads),
|
|
839
888
|
},
|
|
840
889
|
boundary: {
|
|
841
890
|
consumers: {
|
|
@@ -844,6 +893,7 @@ export function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
844
893
|
},
|
|
845
894
|
encapsulated: readManifest(host, targetRoot).declaresExports,
|
|
846
895
|
edges: readEdgeSides(host, targetRoot),
|
|
896
|
+
filledOutside: new Set(projects.flatMap((one) => one.filledOutside)),
|
|
847
897
|
},
|
|
848
898
|
selfCheck,
|
|
849
899
|
intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
|
|
@@ -886,6 +936,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
|
|
|
886
936
|
librariesUnfound: read.librariesUnfound,
|
|
887
937
|
conventionsApplied: read.conventionsApplied,
|
|
888
938
|
unmappedEntries: unmappedEverywhere(read.projects),
|
|
939
|
+
unseenLoads: read.projects.flatMap((one) => one.extra.unseenLoads),
|
|
889
940
|
},
|
|
890
941
|
configured: read.projects.map((one) => one.configured),
|
|
891
942
|
symbols,
|
|
@@ -6,9 +6,11 @@
|
|
|
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
|
-
function isGlobalBlock(symbol, held, files) {
|
|
13
|
+
export function isGlobalBlock(symbol, held, files) {
|
|
12
14
|
return (symbol.kind === "namespace" &&
|
|
13
15
|
files.has(symbol.parent) &&
|
|
14
16
|
symbol.ref.endsWith("#global") &&
|
|
@@ -54,6 +56,66 @@ export function ambientGlobalInterfaces(held, interfaces, scripts) {
|
|
|
54
56
|
? []
|
|
55
57
|
: interfaces.filter((id) => held.ambient.has(id) && holders(id).global);
|
|
56
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) {
|
|
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;
|
|
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
|
+
}
|
|
57
119
|
/**
|
|
58
120
|
* For each reference to a declaration an augmentation block, or the global scope of a
|
|
59
121
|
* file in `scripts` (the files that are no module), holds, a read at the same place of
|