@cplieger/deadset-ts 1.2.0 → 2.0.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 +44 -5
- package/{bin/deadset-ts.ts → dist/bin/deadset-ts.js} +2 -3
- package/dist/bin/node-host.js +74 -0
- package/dist/src/config.js +63 -0
- package/dist/src/discover.js +171 -0
- package/dist/src/host.js +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/inventory.js +676 -0
- package/dist/src/kinds.js +12 -0
- package/dist/src/paths.js +86 -0
- package/dist/src/position.js +52 -0
- package/dist/src/print.js +56 -0
- package/dist/src/ref.js +117 -0
- package/dist/src/references.js +420 -0
- package/dist/src/resolve.js +482 -0
- package/dist/src/run.js +249 -0
- package/dist/src/schema.js +335 -0
- package/dist/src/scope.js +116 -0
- package/dist/src/session.js +101 -0
- package/dist/src/version.js +7 -0
- package/jsr.json +1 -1
- package/package.json +9 -4
- package/src/host.ts +10 -2
- package/src/index.ts +13 -1
- package/src/inventory.ts +26 -3
- package/src/references.ts +606 -0
- package/src/run.ts +4 -4
- package/src/session.ts +41 -22
- package/src/version.ts +3 -18
- package/bin/deadset-ts.mjs +0 -24
- package/bin/node-host.ts +0 -28
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { API } from "@typescript/native/unstable/sync";
|
|
2
|
+
import { isIdentifier } from "@typescript/native/unstable/ast";
|
|
3
|
+
/**
|
|
4
|
+
* Opens the compiler client the analysis reads through.
|
|
5
|
+
*
|
|
6
|
+
* Closing twice releases once: a run that fails before its snapshot opens closes
|
|
7
|
+
* the client where it failed, and the outer release runs whatever happened, so the
|
|
8
|
+
* second call has nothing left to do.
|
|
9
|
+
*/
|
|
10
|
+
export function openEngine(options) {
|
|
11
|
+
const api = new API({ collectTiming: options.collectTiming });
|
|
12
|
+
let closed = false;
|
|
13
|
+
return {
|
|
14
|
+
parseConfigFile: (file) => api.parseConfigFile(file),
|
|
15
|
+
updateSnapshot: (openProjects) => api.updateSnapshot({ openProjects: [...openProjects] }),
|
|
16
|
+
getTimingInfo: () => api.getTimingInfo(),
|
|
17
|
+
close: () => {
|
|
18
|
+
if (closed) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
closed = true;
|
|
22
|
+
api.close();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function viewOf(project) {
|
|
27
|
+
// The snapshot is never updated, so a project's own file set is the same answer
|
|
28
|
+
// every time it is asked for. It is read once because the question is not free:
|
|
29
|
+
// deciding it consults the metadata of every file the program holds, the compiler's
|
|
30
|
+
// own library files included, and a run asks for the set once per pass.
|
|
31
|
+
let ownFiles;
|
|
32
|
+
return {
|
|
33
|
+
configFile: project.configFileName,
|
|
34
|
+
program: project.program,
|
|
35
|
+
checker: project.checker,
|
|
36
|
+
ownSourceFiles: () => {
|
|
37
|
+
ownFiles ??= project.program
|
|
38
|
+
.getSourceFileNames()
|
|
39
|
+
// The metadata decides which files belong to the target, and it is read by
|
|
40
|
+
// name. Fetching each file first and filtering afterwards costs one round
|
|
41
|
+
// trip per library file the answer then discards, and a program holds far
|
|
42
|
+
// more library files than target files.
|
|
43
|
+
.filter((name) => {
|
|
44
|
+
const metadata = project.program.getSourceFileMetadata(name);
|
|
45
|
+
return (metadata !== undefined && !metadata.isDefaultLibrary && !metadata.isFromExternalLibrary);
|
|
46
|
+
})
|
|
47
|
+
.map((name) => project.program.getSourceFile(name))
|
|
48
|
+
.filter((file) => file !== undefined);
|
|
49
|
+
return ownFiles;
|
|
50
|
+
},
|
|
51
|
+
handle: (node) => ({ node }),
|
|
52
|
+
symbolsAt: (handles) => project.checker.getSymbolAtLocation(handles.map(({ node }) => node)),
|
|
53
|
+
resolvedSymbolAt: ({ node }) => isIdentifier(node) ? project.checker.getResolvedSymbol(node) : undefined,
|
|
54
|
+
shorthandValueAt: ({ node }) => project.checker.getShorthandAssignmentValueSymbol(node),
|
|
55
|
+
aliasStepOf: (symbol) => {
|
|
56
|
+
const held = project.checker.getImmediateAliasedSymbol(symbol);
|
|
57
|
+
// An alias that resolves to nothing answers with the checker's own unknown
|
|
58
|
+
// symbol, which declares nothing; a symbol that is no alias answers with
|
|
59
|
+
// nothing at all.
|
|
60
|
+
return held === undefined || held.declarations.length === 0 ? undefined : held;
|
|
61
|
+
},
|
|
62
|
+
declarationAt: (handle) => {
|
|
63
|
+
const node = handle.resolve(project);
|
|
64
|
+
return node === undefined ? undefined : { node };
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
69
|
+
export function diagnosticsOf(project) {
|
|
70
|
+
return {
|
|
71
|
+
configFile: project.configFile,
|
|
72
|
+
syntactic: project.program.getSyntacticDiagnostics(),
|
|
73
|
+
semantic: project.program.getSemanticDiagnostics(),
|
|
74
|
+
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Runs `visit` over every project the compiler configurations name, inside one
|
|
79
|
+
* snapshot of one client.
|
|
80
|
+
*
|
|
81
|
+
* The snapshot is opened once with every configuration, so each becomes a project
|
|
82
|
+
* with its own program and checker; it is never updated, because the analysis
|
|
83
|
+
* reads and the tree does not change under it. It is disposed once when the last
|
|
84
|
+
* project has been visited, and the client is released after it, whether the visit
|
|
85
|
+
* completed or threw.
|
|
86
|
+
*/
|
|
87
|
+
export function runSession(engine, configFiles, visit) {
|
|
88
|
+
try {
|
|
89
|
+
const snapshot = engine.updateSnapshot(configFiles);
|
|
90
|
+
try {
|
|
91
|
+
const projects = snapshot.getProjects().map((project) => visit(viewOf(project)));
|
|
92
|
+
return { projects, timing: engine.getTimingInfo() };
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
snapshot.dispose();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
engine.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Contract version this analyzer implements. It is the resolved value of
|
|
3
|
+
* `contract_version` when no configuration source supplies one, and it moves
|
|
4
|
+
* independently of the analyzer's own version, which is the one the host answers
|
|
5
|
+
* from the package the analyzer was installed from.
|
|
6
|
+
*/
|
|
7
|
+
export const CONTRACT_VERSION = "1.7.0";
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
7
7
|
"license": "GPL-3.0-or-later",
|
|
8
8
|
"bin": {
|
|
9
|
-
"deadset-ts": "./bin/deadset-ts.
|
|
9
|
+
"deadset-ts": "./dist/bin/deadset-ts.js"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
|
-
".":
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"default": "./dist/src/index.js"
|
|
15
|
+
}
|
|
13
16
|
},
|
|
14
17
|
"engines": {
|
|
15
18
|
"node": ">=24"
|
|
16
19
|
},
|
|
17
20
|
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.build.json",
|
|
22
|
+
"prepack": "npm run build",
|
|
18
23
|
"typecheck": "tsc -p tsconfig.json",
|
|
19
24
|
"typecheck:tests": "tsc -p tsconfig.test.json",
|
|
20
25
|
"test": "vitest --run",
|
|
@@ -58,7 +63,7 @@
|
|
|
58
63
|
"files": [
|
|
59
64
|
"LICENSE",
|
|
60
65
|
"NOTICE",
|
|
61
|
-
"
|
|
66
|
+
"dist/",
|
|
62
67
|
"src/",
|
|
63
68
|
"!src/**/*.test.ts",
|
|
64
69
|
"jsr.json"
|
package/src/host.ts
CHANGED
|
@@ -8,8 +8,9 @@ export interface DirectoryEntry {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* The
|
|
12
|
-
* against
|
|
11
|
+
* The platform one run reads: the filesystem, the directory relative paths
|
|
12
|
+
* resolve against, and the version of the package this analyzer was installed
|
|
13
|
+
* from.
|
|
13
14
|
*
|
|
14
15
|
* It is a parameter rather than an import because a published module that reads
|
|
15
16
|
* the filesystem directly carries the whole platform's type surface into every
|
|
@@ -26,4 +27,11 @@ export interface Host {
|
|
|
26
27
|
readDirectory(path: string): readonly DirectoryEntry[];
|
|
27
28
|
/** What one path names, `"absent"` where nothing is there. */
|
|
28
29
|
kindOf(path: string): PathKind;
|
|
30
|
+
/**
|
|
31
|
+
* The version of the package this analyzer was installed from, or a throw
|
|
32
|
+
* naming why it cannot be known. It is a method rather than a value because
|
|
33
|
+
* locating that package is a filesystem walk, and a host that cannot do it
|
|
34
|
+
* says so at the one moment the value is asked for.
|
|
35
|
+
*/
|
|
36
|
+
analyzerVersion(): string;
|
|
29
37
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,12 +2,24 @@ export { run, SETTING_OPTIONS, type Writer } from "./run.ts";
|
|
|
2
2
|
export type { DirectoryEntry, Host, PathKind } from "./host.ts";
|
|
3
3
|
export {
|
|
4
4
|
inventory,
|
|
5
|
+
nodeKey,
|
|
5
6
|
type Inventory,
|
|
6
7
|
type InventoryCost,
|
|
7
8
|
type InventorySymbol,
|
|
8
9
|
type SymbolKind,
|
|
9
10
|
type Visibility,
|
|
10
11
|
} from "./inventory.ts";
|
|
12
|
+
export {
|
|
13
|
+
DEFAULT_BATCH_CAP,
|
|
14
|
+
references,
|
|
15
|
+
type Reference,
|
|
16
|
+
type ReferenceCost,
|
|
17
|
+
type ReferenceOptions,
|
|
18
|
+
type References,
|
|
19
|
+
type Resolution,
|
|
20
|
+
type TestFileRule,
|
|
21
|
+
type Use,
|
|
22
|
+
} from "./references.ts";
|
|
11
23
|
export {
|
|
12
24
|
byPosition,
|
|
13
25
|
positionKey,
|
|
@@ -26,4 +38,4 @@ export {
|
|
|
26
38
|
type Fragment,
|
|
27
39
|
type Module,
|
|
28
40
|
} from "./ref.ts";
|
|
29
|
-
export { CONTRACT_VERSION
|
|
41
|
+
export { CONTRACT_VERSION } from "./version.ts";
|
package/src/inventory.ts
CHANGED
|
@@ -133,6 +133,17 @@ export interface Inventory {
|
|
|
133
133
|
/** The compiler configuration the project was opened from. */
|
|
134
134
|
readonly configFile: string;
|
|
135
135
|
readonly symbols: readonly InventorySymbol[];
|
|
136
|
+
/**
|
|
137
|
+
* Each declaring node's {@link nodeKey} mapped to the identifier of the declaration
|
|
138
|
+
* enumerated there. A reading that starts from a node of the same program, rather
|
|
139
|
+
* than from a position, resolves the declaration through this map instead of
|
|
140
|
+
* rendering the position a second time, so the rule that decides a declaration's
|
|
141
|
+
* identifier has one owner.
|
|
142
|
+
*
|
|
143
|
+
* A getter and a setter of one name are one declaration and two nodes, so both nodes
|
|
144
|
+
* name it.
|
|
145
|
+
*/
|
|
146
|
+
readonly declarations: ReadonlyMap<string, string>;
|
|
136
147
|
readonly cost: InventoryCost;
|
|
137
148
|
/**
|
|
138
149
|
* The module specifiers of the bare star re-exports the project's files carry, in
|
|
@@ -407,8 +418,15 @@ function nameNodeOf(node: Node): Node | undefined {
|
|
|
407
418
|
return (node as { readonly name?: Node }).name;
|
|
408
419
|
}
|
|
409
420
|
|
|
410
|
-
/**
|
|
411
|
-
|
|
421
|
+
/**
|
|
422
|
+
* One node's identity inside one program, which is how two readings of that program
|
|
423
|
+
* name one declaration.
|
|
424
|
+
*
|
|
425
|
+
* The two mechanisms behind the member set resolve a member separately and meet here;
|
|
426
|
+
* so does the reference pass, which starts from a resolved symbol's declaration and
|
|
427
|
+
* needs the declaration this enumeration made for it.
|
|
428
|
+
*/
|
|
429
|
+
export function nodeKey(file: SourceFile, node: Node): string {
|
|
412
430
|
return `${file.fileName}:${String(node.pos)}:${String(node.end)}`;
|
|
413
431
|
}
|
|
414
432
|
|
|
@@ -828,8 +846,10 @@ export function inventory<Brand>(
|
|
|
828
846
|
const endLine = renderPosition(file, targetRoot, Math.max(node.end - 1, 0)).line;
|
|
829
847
|
if (held !== undefined) {
|
|
830
848
|
// A getter and a setter of one name are one symbol and one reference, so the
|
|
831
|
-
// second declaration widens the first record rather than making a second.
|
|
849
|
+
// second declaration widens the first record rather than making a second. Both
|
|
850
|
+
// nodes name that record, so a reading that starts from either one finds it.
|
|
832
851
|
held.endLine = Math.max(held.endLine, endLine);
|
|
852
|
+
byNode.set(nodeKey(file, node), held);
|
|
833
853
|
return;
|
|
834
854
|
}
|
|
835
855
|
const at = name ?? node;
|
|
@@ -884,6 +904,9 @@ export function inventory<Brand>(
|
|
|
884
904
|
return {
|
|
885
905
|
configFile: project.configFile,
|
|
886
906
|
symbols,
|
|
907
|
+
declarations: new Map(
|
|
908
|
+
[...byNode].map(([key, record]) => [key, positionKey(record.position)] as const),
|
|
909
|
+
),
|
|
887
910
|
cost: { batches: 1, exportTables, memberTables },
|
|
888
911
|
starReExports,
|
|
889
912
|
outsideOwnFiles,
|