@cplieger/deadset-ts 1.0.0 → 1.2.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/NOTICE +3 -0
- package/README.md +5 -6
- package/bin/deadset-ts.mjs +24 -0
- package/bin/deadset-ts.ts +2 -1
- package/bin/node-host.ts +28 -0
- package/jsr.json +4 -2
- package/package.json +11 -6
- package/src/config.ts +218 -0
- package/src/discover.ts +198 -0
- package/src/host.ts +29 -0
- package/src/index.ts +29 -1
- package/src/inventory.ts +941 -0
- package/src/kinds.ts +12 -0
- package/src/paths.ts +93 -0
- package/src/position.ts +75 -0
- package/src/print.ts +59 -0
- package/src/ref.ts +179 -0
- package/src/resolve.ts +732 -0
- package/src/run.ts +272 -17
- package/src/schema.ts +401 -0
- package/src/scope.ts +153 -0
- package/src/session.ts +221 -0
- package/src/version.ts +22 -0
package/src/session.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { API } from "@typescript/native/unstable/sync";
|
|
2
|
+
import type {
|
|
3
|
+
Checker,
|
|
4
|
+
Diagnostic,
|
|
5
|
+
DocumentIdentifier,
|
|
6
|
+
NodeHandle,
|
|
7
|
+
Project,
|
|
8
|
+
Program,
|
|
9
|
+
Snapshot,
|
|
10
|
+
Symbol as TSSymbol,
|
|
11
|
+
TimingInfo,
|
|
12
|
+
} from "@typescript/native/unstable/sync";
|
|
13
|
+
import { SyntaxKind } from "@typescript/native/unstable/ast";
|
|
14
|
+
import type { Identifier, Node, SourceFile } from "@typescript/native/unstable/ast";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The compiler the analysis reads through. It is a client over a protocol to a
|
|
18
|
+
* separate process, so every accessor call is a round trip and a run holds one
|
|
19
|
+
* client and one snapshot.
|
|
20
|
+
*
|
|
21
|
+
* It is an interface because the run's own accounting for that lifetime is not on
|
|
22
|
+
* the client's surface: a snapshot reports whether it is disposed and not how many
|
|
23
|
+
* a client opened, so the count is observed at this seam.
|
|
24
|
+
*/
|
|
25
|
+
export interface Engine {
|
|
26
|
+
/**
|
|
27
|
+
* Resolves one compiler configuration file, or throws with the reason it cannot
|
|
28
|
+
* be read. The result is spelled through the client's own signature: the type
|
|
29
|
+
* the compiler package returns here is not one of its exported names.
|
|
30
|
+
*/
|
|
31
|
+
parseConfigFile(file: DocumentIdentifier): ReturnType<API["parseConfigFile"]>;
|
|
32
|
+
/** Opens one snapshot holding every project named. */
|
|
33
|
+
updateSnapshot(openProjects: readonly string[]): Snapshot;
|
|
34
|
+
/** Round-trip latency, bytes transferred and server time, when collection is on. */
|
|
35
|
+
getTimingInfo(): TimingInfo;
|
|
36
|
+
/** Releases the client and the process behind it. */
|
|
37
|
+
close(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** How a run opens its compiler client. */
|
|
41
|
+
export interface EngineOptions {
|
|
42
|
+
/** Collect per-request timing, which the calibration record reads. */
|
|
43
|
+
readonly collectTiming: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Opens the compiler client the analysis reads through.
|
|
48
|
+
*
|
|
49
|
+
* Closing twice releases once: a run that fails before its snapshot opens closes
|
|
50
|
+
* the client where it failed, and the outer release runs whatever happened, so the
|
|
51
|
+
* second call has nothing left to do.
|
|
52
|
+
*/
|
|
53
|
+
export function openEngine(options: EngineOptions): Engine {
|
|
54
|
+
const api = new API({ collectTiming: options.collectTiming });
|
|
55
|
+
let closed = false;
|
|
56
|
+
return {
|
|
57
|
+
parseConfigFile: (file) => api.parseConfigFile(file),
|
|
58
|
+
updateSnapshot: (openProjects) => api.updateSnapshot({ openProjects: [...openProjects] }),
|
|
59
|
+
getTimingInfo: () => api.getTimingInfo(),
|
|
60
|
+
close: () => {
|
|
61
|
+
if (closed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
closed = true;
|
|
65
|
+
api.close();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A node of one project's program, carried with that project's identity.
|
|
72
|
+
*
|
|
73
|
+
* `Brand` is a type parameter and never a value: a view is handed to a visitor
|
|
74
|
+
* that introduces its own `Brand`, so two projects' views have handle types the
|
|
75
|
+
* compiler will not exchange and a handle cannot reach another project's checker.
|
|
76
|
+
* Handles are only meaningful inside the program that produced them, and the
|
|
77
|
+
* cross-project step of the analysis compares rendered positions rather than
|
|
78
|
+
* handles.
|
|
79
|
+
*/
|
|
80
|
+
export interface Handle<Brand> {
|
|
81
|
+
readonly node: Node;
|
|
82
|
+
/** Present only in the type: the project this handle belongs to. */
|
|
83
|
+
readonly project?: Brand;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** One project's program and checker, together with the handles they accept. */
|
|
87
|
+
export interface ProjectView<Brand> {
|
|
88
|
+
/** The compiler configuration file this project was opened from. */
|
|
89
|
+
readonly configFile: string;
|
|
90
|
+
/** The project's program, whose walk is local to this process. */
|
|
91
|
+
readonly program: Program;
|
|
92
|
+
/** The project's checker. Every call on it is a round trip. */
|
|
93
|
+
readonly checker: Checker;
|
|
94
|
+
/** The source files the program holds that are the target's own. */
|
|
95
|
+
ownSourceFiles(): readonly SourceFile[];
|
|
96
|
+
/** A handle to one node of this project's program. */
|
|
97
|
+
handle(node: Node): Handle<Brand>;
|
|
98
|
+
/**
|
|
99
|
+
* The symbols a batch of this project's nodes resolve to, one round trip for
|
|
100
|
+
* the batch, in the order the handles were given. The batch is how a file's
|
|
101
|
+
* identifiers reach the checker, and `undefined` marks a node the batch did not
|
|
102
|
+
* resolve.
|
|
103
|
+
*/
|
|
104
|
+
symbolsAt(handles: readonly Handle<Brand>[]): readonly (TSSymbol | undefined)[];
|
|
105
|
+
/**
|
|
106
|
+
* The node one symbol's declaration handle names, read in this project's program,
|
|
107
|
+
* or `undefined` where this project's program does not hold that file.
|
|
108
|
+
*
|
|
109
|
+
* A symbol is shared by every project of the snapshot and remembers the project it
|
|
110
|
+
* was first seen in, so resolving its handle without saying which program to read
|
|
111
|
+
* would answer out of whichever project reached the symbol first. The project is
|
|
112
|
+
* named here instead, and the answer becomes a handle of this project like any
|
|
113
|
+
* other. The read is local once the file has been fetched.
|
|
114
|
+
*/
|
|
115
|
+
declarationAt(handle: NodeHandle): Handle<Brand> | undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* What a run does with one project. It carries its own type parameter so each
|
|
120
|
+
* invocation gets a distinct `Brand`: a handle taken from one invocation's view is
|
|
121
|
+
* not a handle another invocation's view accepts.
|
|
122
|
+
*/
|
|
123
|
+
export type ProjectVisitor<Result> = <Brand>(project: ProjectView<Brand>) => Result;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The identifiers of one source file, in source order. A file is the unit a batch
|
|
127
|
+
* resolves: the walk is local to this process and the resolution is not, so one
|
|
128
|
+
* file's identifiers are gathered here and handed to the checker together.
|
|
129
|
+
*/
|
|
130
|
+
export function identifiersOf(file: SourceFile): Identifier[] {
|
|
131
|
+
const found: Identifier[] = [];
|
|
132
|
+
const visit = (node: Node): void => {
|
|
133
|
+
if (node.kind === SyntaxKind.Identifier) {
|
|
134
|
+
found.push(node as Identifier);
|
|
135
|
+
}
|
|
136
|
+
node.forEachChild(visit);
|
|
137
|
+
};
|
|
138
|
+
file.forEachChild(visit);
|
|
139
|
+
return found;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function viewOf<Brand>(project: Project): ProjectView<Brand> {
|
|
143
|
+
return {
|
|
144
|
+
configFile: project.configFileName,
|
|
145
|
+
program: project.program,
|
|
146
|
+
checker: project.checker,
|
|
147
|
+
ownSourceFiles: () =>
|
|
148
|
+
project.program
|
|
149
|
+
.getSourceFileNames()
|
|
150
|
+
// The metadata decides which files belong to the target, and it is read by
|
|
151
|
+
// name. Fetching each file first and filtering afterwards costs one round
|
|
152
|
+
// trip per library file the answer then discards, and a program holds far
|
|
153
|
+
// more library files than target files.
|
|
154
|
+
.filter((name) => {
|
|
155
|
+
const metadata = project.program.getSourceFileMetadata(name);
|
|
156
|
+
return (
|
|
157
|
+
metadata !== undefined && !metadata.isDefaultLibrary && !metadata.isFromExternalLibrary
|
|
158
|
+
);
|
|
159
|
+
})
|
|
160
|
+
.map((name) => project.program.getSourceFile(name))
|
|
161
|
+
.filter((file): file is SourceFile => file !== undefined),
|
|
162
|
+
handle: (node) => ({ node }),
|
|
163
|
+
symbolsAt: (handles) => project.checker.getSymbolAtLocation(handles.map(({ node }) => node)),
|
|
164
|
+
declarationAt: (handle) => {
|
|
165
|
+
const node = handle.resolve(project);
|
|
166
|
+
return node === undefined ? undefined : { node };
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Every diagnostic set a project is refused for carrying an error in. */
|
|
172
|
+
export interface ProjectDiagnostics {
|
|
173
|
+
readonly configFile: string;
|
|
174
|
+
readonly syntactic: readonly Diagnostic[];
|
|
175
|
+
readonly semantic: readonly Diagnostic[];
|
|
176
|
+
readonly configParsing: readonly Diagnostic[];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
180
|
+
export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagnostics {
|
|
181
|
+
return {
|
|
182
|
+
configFile: project.configFile,
|
|
183
|
+
syntactic: project.program.getSyntacticDiagnostics(),
|
|
184
|
+
semantic: project.program.getSemanticDiagnostics(),
|
|
185
|
+
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** One run's result: what the visitor answered for each project, and what it cost. */
|
|
190
|
+
export interface SessionResult<Result> {
|
|
191
|
+
readonly projects: readonly Result[];
|
|
192
|
+
readonly timing: TimingInfo;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Runs `visit` over every project the compiler configurations name, inside one
|
|
197
|
+
* snapshot of one client.
|
|
198
|
+
*
|
|
199
|
+
* The snapshot is opened once with every configuration, so each becomes a project
|
|
200
|
+
* with its own program and checker; it is never updated, because the analysis
|
|
201
|
+
* reads and the tree does not change under it. It is disposed once when the last
|
|
202
|
+
* project has been visited, and the client is released after it, whether the visit
|
|
203
|
+
* completed or threw.
|
|
204
|
+
*/
|
|
205
|
+
export function runSession<Result>(
|
|
206
|
+
engine: Engine,
|
|
207
|
+
configFiles: readonly string[],
|
|
208
|
+
visit: ProjectVisitor<Result>,
|
|
209
|
+
): SessionResult<Result> {
|
|
210
|
+
try {
|
|
211
|
+
const snapshot = engine.updateSnapshot(configFiles);
|
|
212
|
+
try {
|
|
213
|
+
const projects = snapshot.getProjects().map((project) => visit(viewOf(project)));
|
|
214
|
+
return { projects, timing: engine.getTimingInfo() };
|
|
215
|
+
} finally {
|
|
216
|
+
snapshot.dispose();
|
|
217
|
+
}
|
|
218
|
+
} finally {
|
|
219
|
+
engine.close();
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import manifest from "../package.json" with { type: "json" };
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Contract version this analyzer implements. It is the resolved value of
|
|
5
|
+
* `contract_version` when no configuration source supplies one, and it moves
|
|
6
|
+
* independently of {@link version}.
|
|
7
|
+
*/
|
|
8
|
+
export const CONTRACT_VERSION = "1.6.0";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This analyzer's own version, which the published package carries rather than
|
|
12
|
+
* the source: the release pipeline writes the released version into the manifest
|
|
13
|
+
* of the artifact it publishes, so an installed package reports the release it
|
|
14
|
+
* came from while a checkout reports the version the manifest holds in git.
|
|
15
|
+
*
|
|
16
|
+
* It is one function because a version written in more than one place is two
|
|
17
|
+
* facts that can disagree: the version verb, the report envelope and anything
|
|
18
|
+
* else that names this analyzer read this one.
|
|
19
|
+
*/
|
|
20
|
+
export function version(): string {
|
|
21
|
+
return manifest.version;
|
|
22
|
+
}
|