@callidescope/graph 0.0.1
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/LICENSE +21 -0
- package/README.md +844 -0
- package/dist/src/index.d.ts +2119 -0
- package/dist/src/index.js +1836 -0
- package/package.json +69 -0
|
@@ -0,0 +1,2119 @@
|
|
|
1
|
+
import { CallableDocumentation } from '@callidescope/core';
|
|
2
|
+
import { CallableId } from '@callidescope/core';
|
|
3
|
+
import { CallableKind } from '@callidescope/core';
|
|
4
|
+
import { CallableNode } from '@callidescope/core';
|
|
5
|
+
import { CallableSignature } from '@callidescope/core';
|
|
6
|
+
import { CallEdge } from '@callidescope/core';
|
|
7
|
+
import { ConsoleLogger } from '@nestjs/common';
|
|
8
|
+
import { default as default_2 } from 'typescript';
|
|
9
|
+
import { EntryPoint } from '@callidescope/core';
|
|
10
|
+
import pino from 'pino';
|
|
11
|
+
import { ResolvedCallidescopeEntryPoints } from '@callidescope/configuration';
|
|
12
|
+
import { SourceLocation } from '@callidescope/core';
|
|
13
|
+
import { StackFrame } from '@callidescope/core';
|
|
14
|
+
import { UnresolvedCall } from '@callidescope/core';
|
|
15
|
+
import { UnresolvedReason } from '@callidescope/core';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Enumerates every simple path above or below one callable.
|
|
19
|
+
*
|
|
20
|
+
* `PathsService` materializes a single deepest path below an entry point,
|
|
21
|
+
* which is the right answer when the question is "how deep can this go".
|
|
22
|
+
* An address-centered lookup asks a different question — everything that
|
|
23
|
+
* calls this, everything this calls — so it walks every path in each
|
|
24
|
+
* direction instead of folding them into one, using `calleeIdsByCaller` for
|
|
25
|
+
* what lies below and `callerIdsByCallee` for what lies above.
|
|
26
|
+
*
|
|
27
|
+
* The walk is iterative, on an explicit stack of partly consumed frames,
|
|
28
|
+
* rather than recursive: the same reasoning `ComponentsService` gives for
|
|
29
|
+
* condensing on an explicit stack applies here, and a widely-called utility
|
|
30
|
+
* can sit many frames deep in a caller chain.
|
|
31
|
+
*/
|
|
32
|
+
export declare class AddressDepthService {
|
|
33
|
+
private readonly pathsService;
|
|
34
|
+
constructor(pathsService: PathsService);
|
|
35
|
+
/** Follows one neighbor: closes a cycle, stops at an unknown id, or descends. */
|
|
36
|
+
private follow;
|
|
37
|
+
/** True when a path holds a frame with an unresolved call beneath it. */
|
|
38
|
+
private isLowerBound;
|
|
39
|
+
/** Advances one traversal frame: closes it, follows a neighbor, or backtracks. */
|
|
40
|
+
private step;
|
|
41
|
+
/** Turns one raw id path into the frames a report can print. */
|
|
42
|
+
private toStack;
|
|
43
|
+
/**
|
|
44
|
+
* Walks every simple path from `startId` through `adjacency`, capped at
|
|
45
|
+
* `MAXIMUM_CALL_ADDRESS_STACKS`.
|
|
46
|
+
*
|
|
47
|
+
* A neighbor already on the current path closes a cycle rather than being
|
|
48
|
+
* followed again: the path so far, plus that repeated neighbor, is emitted
|
|
49
|
+
* as one complete stack, and the walk backtracks instead of looping forever.
|
|
50
|
+
*/
|
|
51
|
+
private traverse;
|
|
52
|
+
/** Traces every path from `startId` down to a leaf. */
|
|
53
|
+
buildDownwardStacks(args: BuildCallAddressStacksArguments): CallAddressTreeResult;
|
|
54
|
+
/** Traces every path from a root caller up to `startId`. */
|
|
55
|
+
buildUpwardStacks(args: BuildCallAddressStacksArguments): CallAddressTreeResult;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolves a human-typed callable address to the callable it names.
|
|
60
|
+
*
|
|
61
|
+
* The address format is the file path plus the qualified name callidescope
|
|
62
|
+
* already prints in every stack, joined by `#`, the same shape a Python
|
|
63
|
+
* traceback or an ESLint rule id uses to point at one symbol in one file —
|
|
64
|
+
* `src/foo.service.ts#FooService.bar`. It is matched against display names
|
|
65
|
+
* rather than the internal `file#offset` id, because that id is never printed
|
|
66
|
+
* anywhere a person seeing this tool's own output would have it to type back
|
|
67
|
+
* in. An optional `:<line>` suffix disambiguates the rare case where a file
|
|
68
|
+
* holds more than one declaration under the same qualified name — two
|
|
69
|
+
* differently-typed overloads, or two callbacks bound to the same property in
|
|
70
|
+
* different branches.
|
|
71
|
+
*/
|
|
72
|
+
export declare class AddressService {
|
|
73
|
+
constructor();
|
|
74
|
+
/** Counts how many candidates were declared on each line. */
|
|
75
|
+
private countCandidatesByLine;
|
|
76
|
+
/** States the accepted shape, in front of whatever went wrong. */
|
|
77
|
+
private describeInvalidAddress;
|
|
78
|
+
/** Finds every discovered callable matching the parsed address. */
|
|
79
|
+
private findMatches;
|
|
80
|
+
/** Splits a raw address into its file path and symbol path, if well formed. */
|
|
81
|
+
private parseAddress;
|
|
82
|
+
/** Splits a `:<line>` disambiguator off the end of a symbol path, if present. */
|
|
83
|
+
private parseSymbolPath;
|
|
84
|
+
/** Writes one callable as the address that names it, with no disambiguator. */
|
|
85
|
+
private toAddress;
|
|
86
|
+
/** Turns matched callables into the candidates an ambiguous result names. */
|
|
87
|
+
private toCandidates;
|
|
88
|
+
/** Resolves a file path, relative to the workspace root, to POSIX form. */
|
|
89
|
+
private toWorkspaceRelative;
|
|
90
|
+
/**
|
|
91
|
+
* Names what an ambiguous address could have meant, and how to pick one.
|
|
92
|
+
*
|
|
93
|
+
* Each candidate is written as the address that would have picked it —
|
|
94
|
+
* built from the declared address, whose file path and qualified name every
|
|
95
|
+
* candidate already agrees on, since that agreement is what made it
|
|
96
|
+
* ambiguous — so the fix is a copy away rather than a file location to go
|
|
97
|
+
* translate back into an address. Any `:<line>` already on the declared
|
|
98
|
+
* address is replaced rather than doubled up.
|
|
99
|
+
*
|
|
100
|
+
* Two declarations on one line are the case no address can separate: the
|
|
101
|
+
* matcher filters on the line, so every candidate carries the same one and
|
|
102
|
+
* the same address would be printed twice. Those name their column instead,
|
|
103
|
+
* which locates them without pretending to be an address, and the advice
|
|
104
|
+
* changes to the only fix there is.
|
|
105
|
+
*
|
|
106
|
+
* Rendered here rather than by each caller, so the refusal a run prints for
|
|
107
|
+
* a declared entry point and the one `depth` and `breadth` print for an
|
|
108
|
+
* address typed at a prompt are one rendering of one concept.
|
|
109
|
+
*/
|
|
110
|
+
describeCandidates(args: {
|
|
111
|
+
address: string;
|
|
112
|
+
candidates: readonly CallableAddressCandidate[];
|
|
113
|
+
}): string;
|
|
114
|
+
/**
|
|
115
|
+
* Every callable a run discovered, written as an address that resolves to it.
|
|
116
|
+
*
|
|
117
|
+
* Rendered here rather than by whoever offers the list, so the shape written
|
|
118
|
+
* out and the shape parsed back in are one class's business and cannot
|
|
119
|
+
* drift. A pair that occurs more than once in a file carries its `:<line>`
|
|
120
|
+
* already, so nothing this returns can come back ambiguous — which is what
|
|
121
|
+
* makes the list safe to pick from blind.
|
|
122
|
+
*/
|
|
123
|
+
listAddresses(callablesById: ReadonlyMap<string, DiscoveredCallable>): string[];
|
|
124
|
+
/** Resolves a callable address against every callable a run discovered. */
|
|
125
|
+
resolve(args: ResolveAddressArguments): CallableAddressResolution;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Name given to a function literal nothing else names. */
|
|
129
|
+
export declare const ANONYMOUS_MEMBER_NAME = "anonymous";
|
|
130
|
+
|
|
131
|
+
/** The call graph, its cycle condensation, and its depth and breadth measurements. */
|
|
132
|
+
export declare interface AssembledGraph {
|
|
133
|
+
readonly breadthMeasurement: BreadthMeasurement;
|
|
134
|
+
readonly condensed: CondensedGraph;
|
|
135
|
+
readonly graph: CallGraph;
|
|
136
|
+
readonly measurement: DepthMeasurement;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Arguments for assembling the call graph and everything derived from it. */
|
|
140
|
+
export declare interface AssembleGraphArguments {
|
|
141
|
+
readonly callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
142
|
+
readonly excludeCallees: readonly string[];
|
|
143
|
+
readonly includeConstructorEdges: boolean;
|
|
144
|
+
readonly workspaceRoot: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** File a package's public surface is declared in. */
|
|
148
|
+
export declare const BARREL_FILE_SUFFIX = "/src/index.ts";
|
|
149
|
+
|
|
150
|
+
/** File a project's runtime entry point lives in. */
|
|
151
|
+
export declare const BOOTSTRAP_FILE_SUFFIX = "/src/main.ts";
|
|
152
|
+
|
|
153
|
+
/** Functions a module bootstraps itself through. */
|
|
154
|
+
export declare const BOOTSTRAP_FUNCTION_NAMES: Set<string>;
|
|
155
|
+
|
|
156
|
+
/** Breadth for every callable measured. */
|
|
157
|
+
export declare interface BreadthMeasurement {
|
|
158
|
+
readonly byCallable: ReadonlyMap<CallableId, CallableBreadth>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Measures how many distinct callables each callable calls directly.
|
|
163
|
+
*
|
|
164
|
+
* Unlike depth, this needs no condensation: a callable's breadth is well
|
|
165
|
+
* defined even when it sits in a cycle, since it only ever looks at its own
|
|
166
|
+
* row in `calleeIdsByCaller` — which `GraphService.assemble` has already
|
|
167
|
+
* deduped and stripped of self-edges.
|
|
168
|
+
*/
|
|
169
|
+
export declare class BreadthService {
|
|
170
|
+
constructor();
|
|
171
|
+
/** Names the callables reached by a list of ids, dropping unknown ones. */
|
|
172
|
+
private toReferences;
|
|
173
|
+
/** Names one callable's direct callees and direct callers. */
|
|
174
|
+
describeDirectCalls(args: {
|
|
175
|
+
callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
176
|
+
graph: CallGraph;
|
|
177
|
+
id: CallableId;
|
|
178
|
+
}): CallableDirectCalls;
|
|
179
|
+
/** Measures the breadth of every named callable. */
|
|
180
|
+
measure(args: MeasureBreadthArguments): BreadthMeasurement;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Arguments for tracing every path in one direction from a callable. */
|
|
184
|
+
export declare interface BuildCallAddressStacksArguments {
|
|
185
|
+
readonly callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
186
|
+
readonly graph: CallGraph;
|
|
187
|
+
readonly startId: CallableId;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Arguments for turning every callable's body into edges. */
|
|
191
|
+
export declare interface BuildEdgesArguments {
|
|
192
|
+
readonly callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
193
|
+
readonly excludeCallees: readonly string[];
|
|
194
|
+
readonly includeConstructorEdges: boolean;
|
|
195
|
+
readonly workspaceRoot: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Arguments for building the set of paths a run will not trace. */
|
|
199
|
+
export declare interface BuildExclusionsArguments {
|
|
200
|
+
readonly exclude: readonly string[];
|
|
201
|
+
readonly excludeFrom: readonly string[];
|
|
202
|
+
readonly workspaceRoot: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Arguments for building the class-hierarchy index. */
|
|
206
|
+
export declare interface BuildHierarchyArguments {
|
|
207
|
+
readonly programs: readonly ProjectProgram[];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Arguments for building a program for every project in a closure. */
|
|
211
|
+
export declare interface BuildProgramsArguments {
|
|
212
|
+
/**
|
|
213
|
+
* The projects a run was asked to trace. Every one of them gets a program,
|
|
214
|
+
* and so does every project their imports transitively reach.
|
|
215
|
+
*/
|
|
216
|
+
readonly startingProjects: readonly WorkspaceProject[];
|
|
217
|
+
/**
|
|
218
|
+
* Every project the workspace holds, which is what lets a file one program
|
|
219
|
+
* pulled in name the project that owns it — including a project no starting
|
|
220
|
+
* root mentions. Nothing here is built unless the closure reaches it.
|
|
221
|
+
*/
|
|
222
|
+
readonly workspaceProjects: readonly WorkspaceProject[];
|
|
223
|
+
readonly workspaceRoot: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Arguments for layering each project's own exclusions over a run's. */
|
|
227
|
+
export declare interface BuildProjectFileFilterArguments {
|
|
228
|
+
/**
|
|
229
|
+
* The globs each project declared for itself, keyed by project name, exactly
|
|
230
|
+
* as that project's own configuration file wrote them.
|
|
231
|
+
*
|
|
232
|
+
* A project that declared none is absent rather than present with an empty
|
|
233
|
+
* array, so an empty map means no project in the run excludes anything and
|
|
234
|
+
* the run's own filter is handed straight back.
|
|
235
|
+
*/
|
|
236
|
+
readonly excludeByProject: ReadonlyMap<string, readonly string[]>;
|
|
237
|
+
/** The run's own filter, which every project's globs are layered over. */
|
|
238
|
+
readonly fileFilter: FileFilter;
|
|
239
|
+
/**
|
|
240
|
+
* Every project the run reached, so a file is judged by the project that
|
|
241
|
+
* owns it rather than by whichever declaring root happens to contain it.
|
|
242
|
+
*/
|
|
243
|
+
readonly projects: readonly WorkspaceProject[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** One declaration an ambiguous address could have meant. */
|
|
247
|
+
export declare interface CallableAddressCandidate {
|
|
248
|
+
readonly id: CallableId;
|
|
249
|
+
readonly location: SourceLocation;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* What resolving a callable address produced.
|
|
254
|
+
*
|
|
255
|
+
* A union rather than an optional id: the four outcomes are told apart by
|
|
256
|
+
* what a caller does next — proceed, or print exactly one of four different
|
|
257
|
+
* messages — and a single `id: CallableId | undefined` cannot carry which of
|
|
258
|
+
* the three failures happened.
|
|
259
|
+
*/
|
|
260
|
+
export declare type CallableAddressResolution = {
|
|
261
|
+
readonly candidates: readonly CallableAddressCandidate[];
|
|
262
|
+
readonly kind: "ambiguous";
|
|
263
|
+
} | {
|
|
264
|
+
readonly id: CallableId;
|
|
265
|
+
readonly kind: "resolved";
|
|
266
|
+
} | {
|
|
267
|
+
readonly kind: "invalid";
|
|
268
|
+
readonly reason: string;
|
|
269
|
+
} | {
|
|
270
|
+
readonly kind: "not-found";
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* How many callables one callable calls directly, and which ones.
|
|
275
|
+
*
|
|
276
|
+
* `calleeIds` holds the distinct callables reached, not call sites: `assemble`
|
|
277
|
+
* already dedupes repeat calls and drops self-edges when it builds
|
|
278
|
+
* `calleeIdsByCaller`, so breadth inherits both properties for free.
|
|
279
|
+
*/
|
|
280
|
+
export declare interface CallableBreadth {
|
|
281
|
+
readonly breadth: number;
|
|
282
|
+
readonly calleeIds: readonly CallableId[];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Everything the walk over owned files produced. */
|
|
286
|
+
export declare interface CallableCollection {
|
|
287
|
+
readonly byId: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
288
|
+
readonly fileCount: number;
|
|
289
|
+
/** How many files each project contributed, for its own report. */
|
|
290
|
+
readonly fileCountByProject: ReadonlyMap<string, number>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Any declaration the tool treats as a frame on a call stack. */
|
|
294
|
+
export declare type CallableDeclaration = default_2.ArrowFunction | default_2.ConstructorDeclaration | default_2.FunctionDeclaration | default_2.FunctionExpression | default_2.GetAccessorDeclaration | default_2.MethodDeclaration | default_2.SetAccessorDeclaration;
|
|
295
|
+
|
|
296
|
+
/** The direct callees and direct callers of one addressed callable. */
|
|
297
|
+
export declare interface CallableDirectCalls {
|
|
298
|
+
readonly callees: readonly CallableReference[];
|
|
299
|
+
readonly callers: readonly CallableReference[];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Gives every callable a stable identifier and a name a report can print.
|
|
304
|
+
*
|
|
305
|
+
* The identifier is the file path plus the declaration's start offset, not its
|
|
306
|
+
* line. Two callables share a line often — an arrow property and the statement
|
|
307
|
+
* holding it do — but exactly one declaration can begin at one offset, so an
|
|
308
|
+
* offset cannot collide the way a line can.
|
|
309
|
+
*/
|
|
310
|
+
export declare class CallableIdentityService {
|
|
311
|
+
constructor();
|
|
312
|
+
/**
|
|
313
|
+
* Names a function literal by the call it was passed to.
|
|
314
|
+
*
|
|
315
|
+
* `ExampleService.load → map(…)` reads far better in a stack than
|
|
316
|
+
* `anonymous`, and it is the only name a callback ever really has.
|
|
317
|
+
*/
|
|
318
|
+
private describeCallbackArgument;
|
|
319
|
+
/** Reads the name a property, variable, or parameter declaration binds. */
|
|
320
|
+
private readBindingName;
|
|
321
|
+
/**
|
|
322
|
+
* Classifies a function literal by whatever binds it.
|
|
323
|
+
*
|
|
324
|
+
* A literal has no shape of its own worth naming — what it is depends
|
|
325
|
+
* entirely on where it was written down.
|
|
326
|
+
*/
|
|
327
|
+
private readBoundKind;
|
|
328
|
+
/** Builds the identifier for one declaration. */
|
|
329
|
+
buildId(args: {
|
|
330
|
+
declaration: CallableDeclaration;
|
|
331
|
+
workspaceRelativePath: string;
|
|
332
|
+
}): string;
|
|
333
|
+
/** Counts the statements in a body, as a proxy for how much it does. */
|
|
334
|
+
countStatements(declaration: CallableDeclaration): number;
|
|
335
|
+
/**
|
|
336
|
+
* True when a declaration is reachable from outside its own file.
|
|
337
|
+
*
|
|
338
|
+
* Three shapes carry the keyword, and only one of them carries it on the
|
|
339
|
+
* declaration the graph holds. A method's `export` sits on its class, and an
|
|
340
|
+
* arrow constant's sits on the variable statement above the arrow — the shape
|
|
341
|
+
* a React component, an anchor builder, and most barrel exports are written
|
|
342
|
+
* in. Reading the arrow's own modifiers alone leaves every one of them
|
|
343
|
+
* looking unexported, and so promoted as an orphan rather than classified as
|
|
344
|
+
* the barrel export it is.
|
|
345
|
+
*/
|
|
346
|
+
isExported(declaration: CallableDeclaration): boolean;
|
|
347
|
+
/** Builds the qualified name a report prints for a callable. */
|
|
348
|
+
readDisplayName(declaration: CallableDeclaration): string;
|
|
349
|
+
/** Names the class or interface a declaration is a member of, if any. */
|
|
350
|
+
readEnclosingTypeName(declaration: CallableDeclaration): string | undefined;
|
|
351
|
+
/** Classifies a declaration by the shape it was written in. */
|
|
352
|
+
readKind(declaration: CallableDeclaration): CallableKind;
|
|
353
|
+
/** Reads the one-based line and column a declaration starts at. */
|
|
354
|
+
readLocation(args: {
|
|
355
|
+
declaration: CallableDeclaration;
|
|
356
|
+
workspaceRelativePath: string;
|
|
357
|
+
}): SourceLocation;
|
|
358
|
+
/** Reads the member name, falling back to the shape it was written in. */
|
|
359
|
+
readMemberName(declaration: CallableDeclaration): string;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** One callable named alongside where it is declared. */
|
|
363
|
+
export declare interface CallableReference {
|
|
364
|
+
readonly displayName: string;
|
|
365
|
+
readonly id: CallableId;
|
|
366
|
+
readonly location: SourceLocation;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Provides discovery and naming of every callable in the workspace.
|
|
371
|
+
*/
|
|
372
|
+
export declare class CallablesModule {
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Walks every owned file and collects the callables a call stack can hold.
|
|
377
|
+
*
|
|
378
|
+
* Overload signatures are skipped in favour of the one declaration with a body:
|
|
379
|
+
* a signature is a promise about a call, not a frame the runtime ever pushes.
|
|
380
|
+
*/
|
|
381
|
+
export declare class CallablesService {
|
|
382
|
+
private readonly callableIdentityService;
|
|
383
|
+
private readonly programService;
|
|
384
|
+
private readonly workspaceService;
|
|
385
|
+
constructor(callableIdentityService: CallableIdentityService, programService: ProgramService, workspaceService: WorkspaceService);
|
|
386
|
+
/** Collects every callable declaration in one source file. */
|
|
387
|
+
private collectFromFile;
|
|
388
|
+
/**
|
|
389
|
+
* Walks the files one program owns, skipping the ones it does not.
|
|
390
|
+
*
|
|
391
|
+
* Iterating the program's own source files rather than looking each owned
|
|
392
|
+
* path up in it. A workspace reached through a symlink — a macOS temp
|
|
393
|
+
* directory, a pnpm store — hands the program one spelling of a path and the
|
|
394
|
+
* ownership map another, and a lookup would then quietly find nothing rather
|
|
395
|
+
* than fail.
|
|
396
|
+
*/
|
|
397
|
+
private collectFromProgram;
|
|
398
|
+
/** Turns one declaration into a fully described node. */
|
|
399
|
+
private describe;
|
|
400
|
+
/** True when a node is a declaration this tool treats as a frame. */
|
|
401
|
+
private isCallableDeclaration;
|
|
402
|
+
/** Returns the workspace-relative path, or nothing when unowned. */
|
|
403
|
+
private readOwnedPath;
|
|
404
|
+
/**
|
|
405
|
+
* Walks every owned file and returns the callables found, keyed by id.
|
|
406
|
+
*
|
|
407
|
+
* Each file is walked by the one program that owns it, so a package shared by
|
|
408
|
+
* several projects contributes its callables once rather than once per
|
|
409
|
+
* dependent.
|
|
410
|
+
*/
|
|
411
|
+
collect(args: CollectCallablesArguments): CallableCollection;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** One simple path traced above or below an addressed callable. */
|
|
415
|
+
export declare interface CallAddressStack {
|
|
416
|
+
readonly frames: readonly StackFrame[];
|
|
417
|
+
/**
|
|
418
|
+
* True when a frame on this path holds an unresolved call, making the path
|
|
419
|
+
* a floor rather than a complete picture of what lies beyond it.
|
|
420
|
+
*/
|
|
421
|
+
readonly isLowerBound: boolean;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Every path traced in one direction, and whether the walk was capped. */
|
|
425
|
+
export declare interface CallAddressTreeResult {
|
|
426
|
+
readonly stacks: readonly CallAddressStack[];
|
|
427
|
+
/** True when `MAXIMUM_CALL_ADDRESS_STACKS` was reached before the walk finished. */
|
|
428
|
+
readonly truncated: boolean;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The assembled call graph, indexed both ways. */
|
|
432
|
+
export declare interface CallGraph {
|
|
433
|
+
readonly calleeIdsByCaller: ReadonlyMap<CallableId, readonly CallableId[]>;
|
|
434
|
+
readonly callerIdsByCallee: ReadonlyMap<CallableId, readonly CallableId[]>;
|
|
435
|
+
readonly edges: readonly CallEdge[];
|
|
436
|
+
/** Callables holding at least one call that could not be followed. */
|
|
437
|
+
readonly unresolvedCallerIds: ReadonlySet<CallableId>;
|
|
438
|
+
readonly unresolvedCalls: readonly UnresolvedCall[];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** A call expression found inside one callable's own body. */
|
|
442
|
+
export declare interface CallSite {
|
|
443
|
+
readonly expression: default_2.CallExpression | default_2.NewExpression;
|
|
444
|
+
/** Function literals passed as arguments, each its own frame. */
|
|
445
|
+
readonly functionArguments: readonly default_2.SignatureDeclaration[];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Finds the calls one callable makes, without descending into nested bodies.
|
|
450
|
+
*
|
|
451
|
+
* A function literal inside a body is its own frame, not part of the enclosing
|
|
452
|
+
* one, so the walk stops at it. Descending would attribute a callback's calls
|
|
453
|
+
* to whichever function happened to contain the callback's text, which is how
|
|
454
|
+
* a shallow orchestrator ends up looking like the deepest thing in the file.
|
|
455
|
+
*/
|
|
456
|
+
export declare class CallSitesService {
|
|
457
|
+
constructor();
|
|
458
|
+
/** True when a node opens a new frame and the walk should stop. */
|
|
459
|
+
private isNestedBody;
|
|
460
|
+
/** Collects the function literals passed as arguments to one call. */
|
|
461
|
+
private readFunctionArguments;
|
|
462
|
+
/** Collects every call this declaration's own body makes. */
|
|
463
|
+
collect(declaration: CallableDeclaration): CallSite[];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Provides the lookups edge resolution consults for every call site.
|
|
468
|
+
*/
|
|
469
|
+
export declare class ClassesModule {
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Resolves an interface or abstract member to the classes that implement it.
|
|
474
|
+
*
|
|
475
|
+
* The language service can answer this directly, and answering it that way
|
|
476
|
+
* costs milliseconds per call site — minutes across a repository. This builds
|
|
477
|
+
* one index up front instead, and answers from it in constant time.
|
|
478
|
+
*
|
|
479
|
+
* The index has to be structural, not just nominal. Classes in this repository
|
|
480
|
+
* routinely satisfy an interface without writing `implements`, and the
|
|
481
|
+
* interface members themselves are usually arrow-typed properties rather than
|
|
482
|
+
* method signatures. An index that only followed `implements` clauses would
|
|
483
|
+
* find none of them.
|
|
484
|
+
*/
|
|
485
|
+
export declare class ClassesService {
|
|
486
|
+
private readonly externalService;
|
|
487
|
+
constructor(externalService: ExternalService);
|
|
488
|
+
private readonly checkerByClass;
|
|
489
|
+
/** Every traced class that declares a member of a given name. */
|
|
490
|
+
private readonly classesByMemberName;
|
|
491
|
+
/** Classes naming a base in an `implements` or `extends` clause. */
|
|
492
|
+
private readonly derivedByBaseName;
|
|
493
|
+
private readonly lookupCache;
|
|
494
|
+
/**
|
|
495
|
+
* Walks the whole inheritance chain below a base, not just its children.
|
|
496
|
+
*
|
|
497
|
+
* An abstract base is often extended by another abstract class, with the
|
|
498
|
+
* concrete implementation another level down. Following only direct
|
|
499
|
+
* subclasses finds the intermediate one, discards it for still being
|
|
500
|
+
* abstract, and reports that nothing implements the member.
|
|
501
|
+
*/
|
|
502
|
+
private collectDerived;
|
|
503
|
+
/** Keeps only classes whose instance type satisfies the declaring type. */
|
|
504
|
+
private filterAssignable;
|
|
505
|
+
/** Records one class under every base type it names. */
|
|
506
|
+
private indexHeritage;
|
|
507
|
+
/** Records one class under every member name it declares. */
|
|
508
|
+
private indexMembers;
|
|
509
|
+
/** Reads one member's concrete declarations off a candidate class. */
|
|
510
|
+
private readMemberDeclarations;
|
|
511
|
+
/** Walks every traced class once, recording members and heritage. */
|
|
512
|
+
build(args: BuildHierarchyArguments): void;
|
|
513
|
+
/** Indexes the classes one program owns. */
|
|
514
|
+
indexProgram(projectProgram: ProjectProgram): void;
|
|
515
|
+
/**
|
|
516
|
+
* Finds the concrete declarations one interface member resolves to.
|
|
517
|
+
*
|
|
518
|
+
* Nominal implementers are preferred; when a base names none — the common
|
|
519
|
+
* case here — every class declaring a member of that name is tested for
|
|
520
|
+
* assignability instead. Filtering by member name first is what keeps that
|
|
521
|
+
* sweep cheap enough to run.
|
|
522
|
+
*/
|
|
523
|
+
resolveImplementations(args: {
|
|
524
|
+
checker: default_2.TypeChecker;
|
|
525
|
+
memberName: string;
|
|
526
|
+
ownerName: string;
|
|
527
|
+
ownerSymbol: default_2.Symbol;
|
|
528
|
+
}): ImplementationLookup;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Arguments for collecting every callable a run will trace. */
|
|
532
|
+
export declare interface CollectCallablesArguments {
|
|
533
|
+
readonly fileFilter: FileFilter;
|
|
534
|
+
/** What a project that declared no entry-point rules of its own is judged by. */
|
|
535
|
+
readonly includeTests: boolean;
|
|
536
|
+
/**
|
|
537
|
+
* The answer each project declared for itself, keyed by project name.
|
|
538
|
+
*
|
|
539
|
+
* A project absent here is judged by `includeTests`, exactly the way a
|
|
540
|
+
* project absent from `entryPointsByProject` is judged by the run's own
|
|
541
|
+
* entry-point rules — this is the same declaration read at the one layer
|
|
542
|
+
* that can act on it, since which files a run even looks at is settled here
|
|
543
|
+
* rather than when roots are chosen.
|
|
544
|
+
*/
|
|
545
|
+
readonly includeTestsByProject: ReadonlyMap<string, boolean>;
|
|
546
|
+
readonly ownerByFilePath: ReadonlyMap<string, ProjectProgram>;
|
|
547
|
+
readonly workspaceRoot: string;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** The method a decorated command class exposes to its runner. */
|
|
551
|
+
export declare const COMMAND_RUNNER_METHOD_NAME = "run";
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Builds compiler hosts that share parsed source files across programs.
|
|
555
|
+
*
|
|
556
|
+
* Every project's program pulls in the same lib files and the same workspace
|
|
557
|
+
* dependencies, so without sharing, one run re-reads `lib.es2023.d.ts` once per
|
|
558
|
+
* project and the shared packages roughly one and a half times over. Caching is
|
|
559
|
+
* the difference between a ten-second run and a five-second one, and it costs
|
|
560
|
+
* nothing in correctness as long as the cache key names the script target.
|
|
561
|
+
*/
|
|
562
|
+
export declare class CompilerHostService {
|
|
563
|
+
constructor();
|
|
564
|
+
private readonly moduleResolutionCaches;
|
|
565
|
+
/**
|
|
566
|
+
* Keyed on file name and script target together.
|
|
567
|
+
*
|
|
568
|
+
* The target has to be part of the key. A source file parsed at ES2023 has a
|
|
569
|
+
* different tree from the same text parsed at ES5, so handing a cached one to
|
|
570
|
+
* a program that asked for another target is silently wrong.
|
|
571
|
+
*/
|
|
572
|
+
private readonly sourceFileCache;
|
|
573
|
+
/** Returns the module resolution cache for one working directory. */
|
|
574
|
+
private resolveModuleCache;
|
|
575
|
+
/** Drops every cached source file, for a host that outlives one run. */
|
|
576
|
+
clear(): void;
|
|
577
|
+
/**
|
|
578
|
+
* Creates a compiler host that reuses already-parsed source files.
|
|
579
|
+
*
|
|
580
|
+
* `setParentNodes` is on because the rest of the package reads `node.parent`
|
|
581
|
+
* and `node.getStart()`, neither of which works on a tree parsed without it,
|
|
582
|
+
* and neither of which `createProgram` turns on by itself for files it pulls
|
|
583
|
+
* in rather than files it was handed.
|
|
584
|
+
*/
|
|
585
|
+
createHost(args: {
|
|
586
|
+
options: default_2.CompilerOptions;
|
|
587
|
+
workspaceRoot: string;
|
|
588
|
+
}): default_2.CompilerHost;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Memoized longest-path result for one component. */
|
|
592
|
+
export declare interface ComponentDepth {
|
|
593
|
+
/** The successor lying on the deepest path, for O(depth) reconstruction. */
|
|
594
|
+
readonly deepestSuccessor: number | undefined;
|
|
595
|
+
readonly depth: number;
|
|
596
|
+
/** True when the deepest path runs through an unfollowable call. */
|
|
597
|
+
readonly reachesUnresolved: boolean;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Collapses every cycle in the call graph into a single node.
|
|
602
|
+
*
|
|
603
|
+
* Longest path is only defined on an acyclic graph, so recursion has to be
|
|
604
|
+
* dealt with before depth is measured rather than during it. The obvious
|
|
605
|
+
* alternative — detecting a repeat visit while walking — makes the memoized
|
|
606
|
+
* depth depend on which path happened to arrive first, so the same function
|
|
607
|
+
* reports different depths from different entry points and between runs. A
|
|
608
|
+
* linter whose numbers move on their own is not usable as a gate.
|
|
609
|
+
*
|
|
610
|
+
* Condensing instead gives every cycle one cost, counted once: three functions
|
|
611
|
+
* calling each other in a ring contribute three frames, which is an honest
|
|
612
|
+
* floor on a stack that has no ceiling.
|
|
613
|
+
*
|
|
614
|
+
* The traversal is iterative because the graph is deep enough that a recursive
|
|
615
|
+
* one risks blowing the JavaScript stack while measuring stack depth.
|
|
616
|
+
*/
|
|
617
|
+
export declare class ComponentsService {
|
|
618
|
+
constructor();
|
|
619
|
+
/**
|
|
620
|
+
* Closes a completed node, emitting a component when it is a root.
|
|
621
|
+
*
|
|
622
|
+
* A node whose low link never moved below its own discovery order is the
|
|
623
|
+
* entry point of a strongly connected component, and everything above it on
|
|
624
|
+
* the pending stack belongs to that component.
|
|
625
|
+
*/
|
|
626
|
+
private closeNode;
|
|
627
|
+
/** Pops a finished frame, emitting its component and lifting its low link. */
|
|
628
|
+
private finishFrame;
|
|
629
|
+
/** Propagates a finished child's low link into its parent. */
|
|
630
|
+
private liftLowLink;
|
|
631
|
+
/** Records the discovery order of a node and pushes it onto the stack. */
|
|
632
|
+
private openNode;
|
|
633
|
+
/** Advances the traversal by one step from the given frame. */
|
|
634
|
+
private step;
|
|
635
|
+
/** Descends into one successor, or folds it back if already seen. */
|
|
636
|
+
private visitSuccessor;
|
|
637
|
+
/** Lifts the callable-level edges onto the condensed components. */
|
|
638
|
+
buildSuccessors(args: {
|
|
639
|
+
componentIdByCallable: ReadonlyMap<CallableId, number>;
|
|
640
|
+
graph: CallGraph;
|
|
641
|
+
memberIdsByComponent: readonly (readonly CallableId[])[];
|
|
642
|
+
}): ReadonlySet<number>[];
|
|
643
|
+
/** Condenses the graph, returning components in reverse topological order. */
|
|
644
|
+
condense(args: {
|
|
645
|
+
callableIds: Iterable<CallableId, undefined, undefined>;
|
|
646
|
+
graph: CallGraph;
|
|
647
|
+
}): CondensedGraph;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** A call through a computed member name, which nothing can follow. */
|
|
651
|
+
export declare const COMPUTED_MEMBER_CALL: ResolvedCallSite;
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* The graph with each cycle collapsed into one node.
|
|
655
|
+
*
|
|
656
|
+
* Longest path is only well defined on an acyclic graph, so recursion is
|
|
657
|
+
* condensed away before depth is computed rather than special-cased during it.
|
|
658
|
+
*/
|
|
659
|
+
export declare interface CondensedGraph {
|
|
660
|
+
readonly componentIdByCallable: ReadonlyMap<CallableId, number>;
|
|
661
|
+
readonly memberIdsByComponent: readonly (readonly CallableId[])[];
|
|
662
|
+
readonly successorsByComponent: readonly ReadonlySet<number>[];
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** The tag that marks a callable as on its way out. */
|
|
666
|
+
export declare const DEPRECATED_TAG = "deprecated";
|
|
667
|
+
|
|
668
|
+
/** Depth for every component in the condensation. */
|
|
669
|
+
export declare interface DepthMeasurement {
|
|
670
|
+
readonly byComponent: readonly ComponentDepth[];
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** Arguments for naming one declaration. */
|
|
674
|
+
export declare interface DescribeCallableArguments {
|
|
675
|
+
readonly declaration: CallableDeclaration;
|
|
676
|
+
readonly projectProgram: ProjectProgram;
|
|
677
|
+
readonly workspaceRelativePath: string;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** One discovered callable, kept alongside the node it was found at. */
|
|
681
|
+
export declare interface DiscoveredCallable {
|
|
682
|
+
readonly declaration: CallableDeclaration;
|
|
683
|
+
readonly node: CallableNode;
|
|
684
|
+
readonly projectProgram: ProjectProgram;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** Arguments for discovering the projects a run will trace. */
|
|
688
|
+
export declare interface DiscoverProjectsArguments {
|
|
689
|
+
/**
|
|
690
|
+
* Project directories to trace, workspace-relative or absolute. Every
|
|
691
|
+
* directory under the workspace root holding its own `tsconfig.json`
|
|
692
|
+
* when empty.
|
|
693
|
+
*/
|
|
694
|
+
readonly directories: readonly string[];
|
|
695
|
+
/**
|
|
696
|
+
* Decides which projects a run will not trace, judged on the
|
|
697
|
+
* `tsconfig.json` that would identify each one.
|
|
698
|
+
*
|
|
699
|
+
* Applied here rather than only to the files a program yields, because a
|
|
700
|
+
* `tsconfig.json` an exclusion already names should never be read at all —
|
|
701
|
+
* one that does not parse, belonging to a directory nobody asked to trace,
|
|
702
|
+
* is not a reason for the run to say anything.
|
|
703
|
+
*/
|
|
704
|
+
readonly fileFilter?: FileFilter | undefined;
|
|
705
|
+
readonly workspaceRoot: string;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Provides the prose a report prints beneath a frame.
|
|
710
|
+
*/
|
|
711
|
+
export declare class DocumentationModule {
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Reads what a callable's documentation comment says about it.
|
|
716
|
+
*
|
|
717
|
+
* Read through the type checker rather than the comment trivia above the node,
|
|
718
|
+
* which is what makes it right on the shapes this repository writes. An
|
|
719
|
+
* overload's comment sits on the signature rather than the implementation the
|
|
720
|
+
* graph points at, and an arrow-typed member's sits on the property rather than
|
|
721
|
+
* the arrow — the checker resolves both to the same symbol, and scanning
|
|
722
|
+
* trivia would find neither.
|
|
723
|
+
*/
|
|
724
|
+
export declare class DocumentationService {
|
|
725
|
+
constructor();
|
|
726
|
+
/**
|
|
727
|
+
* Collapses a documentation comment onto one line, in full.
|
|
728
|
+
*
|
|
729
|
+
* Nothing is shortened here. A comment is what it is, and how much of it
|
|
730
|
+
* fits is a question about the thing being rendered into — an indented tree
|
|
731
|
+
* in a terminal has an answer, and a JSON report consumed by a machine does
|
|
732
|
+
* not. Shortening at the point of reading would impose the terminal's answer
|
|
733
|
+
* on every consumer, so `ReportService` decides instead.
|
|
734
|
+
*/
|
|
735
|
+
private readSummary;
|
|
736
|
+
/** Resolves the symbol a declaration's documentation hangs off. */
|
|
737
|
+
private readSymbol;
|
|
738
|
+
/**
|
|
739
|
+
* Reads the documentation comment, if the callable has one.
|
|
740
|
+
*
|
|
741
|
+
* A comment that is nothing but tags — `@deprecated` on its own — leaves the
|
|
742
|
+
* summary empty, so the tags are read separately rather than inferred from
|
|
743
|
+
* whether there was any prose to find.
|
|
744
|
+
*/
|
|
745
|
+
read(args: ReadDocumentationArguments): CallableDocumentation | undefined;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** A call whose callee could not be identified at all. */
|
|
749
|
+
export declare const DYNAMIC_CALL: ResolvedCallSite;
|
|
750
|
+
|
|
751
|
+
/** Every edge a run resolved, and every call site it could not. */
|
|
752
|
+
export declare interface EdgeCollection {
|
|
753
|
+
readonly edges: readonly CallEdge[];
|
|
754
|
+
readonly unresolvedCalls: readonly UnresolvedCall[];
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Provides call-site discovery and resolution into call-graph edges.
|
|
759
|
+
*/
|
|
760
|
+
export declare class EdgesModule {
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Turns every callable's body into edges in the call graph.
|
|
765
|
+
*
|
|
766
|
+
* Only calls landing on traced code become edges. A call into a dependency is a
|
|
767
|
+
* leaf: whether `Array.prototype.map` is deeply implemented is not a fact about
|
|
768
|
+
* whether this repository's layering is too deep, and counting it would make
|
|
769
|
+
* every reported number move on an unrelated upgrade.
|
|
770
|
+
*/
|
|
771
|
+
export declare class EdgesService {
|
|
772
|
+
private readonly callSitesService;
|
|
773
|
+
private readonly externalService;
|
|
774
|
+
private readonly programService;
|
|
775
|
+
private readonly symbolResolutionService;
|
|
776
|
+
private readonly workspaceService;
|
|
777
|
+
private readonly logger;
|
|
778
|
+
constructor(callSitesService: CallSitesService, externalService: ExternalService, programService: ProgramService, symbolResolutionService: SymbolResolutionService, workspaceService: WorkspaceService, logger: LoggerService);
|
|
779
|
+
/** Turns one call site into the edges and non-resolutions it produced. */
|
|
780
|
+
private buildSiteEdges;
|
|
781
|
+
/** Records the function literals one call site passes as arguments. */
|
|
782
|
+
private collectCallbackEdges;
|
|
783
|
+
/**
|
|
784
|
+
* Whether a callee's own display name matches a configured exclusion glob.
|
|
785
|
+
*
|
|
786
|
+
* Matched against `Type.member` rather than against a path: a
|
|
787
|
+
* cross-cutting callable like a logger
|
|
788
|
+
* has no single file worth naming, but every one of its call sites shares
|
|
789
|
+
* the same display name.
|
|
790
|
+
*/
|
|
791
|
+
private isExcludedCallee;
|
|
792
|
+
/** Reads the one-based position of a node, for a report. */
|
|
793
|
+
private readLocation;
|
|
794
|
+
/**
|
|
795
|
+
* Maps a resolved declaration to the callable it belongs to.
|
|
796
|
+
*
|
|
797
|
+
* A resolution can land on a property or variable declaration whose
|
|
798
|
+
* initializer is the real function, which is how this repository writes both
|
|
799
|
+
* arrow-typed class members and object-literal methods.
|
|
800
|
+
*/
|
|
801
|
+
private resolveCallableId;
|
|
802
|
+
/** Resolves one call site, choosing the right strategy for its shape. */
|
|
803
|
+
private resolveSite;
|
|
804
|
+
/** Builds every edge in the graph, and records the calls it could not. */
|
|
805
|
+
build(args: BuildEdgesArguments): EdgeCollection;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Provides the rules deciding which callables root a call stack.
|
|
810
|
+
*/
|
|
811
|
+
export declare class EntriesModule {
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Decides which callables are the roots of a call stack.
|
|
816
|
+
*
|
|
817
|
+
* This is where the tool is most likely to be wrong, and the numbers it reports
|
|
818
|
+
* are only as meaningful as this list. Most code in a repository like this one
|
|
819
|
+
* is never called by anything inside it — a framework calls it. So the roots
|
|
820
|
+
* have to be named, and anything missed would silently vanish from every
|
|
821
|
+
* measurement rather than fail loudly.
|
|
822
|
+
*
|
|
823
|
+
* A configuration can also name a root outright, by the same address the
|
|
824
|
+
* lookup commands take, for the surface no rule here can infer. Those are
|
|
825
|
+
* additive: they never turn a rule off.
|
|
826
|
+
*
|
|
827
|
+
* Orphan promotion is the guard against exactly that. After the named rules
|
|
828
|
+
* run, anything with no caller left in the graph becomes a root anyway, so a
|
|
829
|
+
* rule this list is missing shows up as an orphan instead of as a hole.
|
|
830
|
+
*/
|
|
831
|
+
export declare class EntriesService {
|
|
832
|
+
private readonly addressService;
|
|
833
|
+
private readonly logger;
|
|
834
|
+
constructor(addressService: AddressService, logger: LoggerService);
|
|
835
|
+
/** Classifies a callable's declaration, or leaves it unrooted. */
|
|
836
|
+
private classify;
|
|
837
|
+
/** Roots every callable one of the entry-point rules recognizes. */
|
|
838
|
+
private classifyEveryCallable;
|
|
839
|
+
/** True when a node carries one of the configured framework decorators. */
|
|
840
|
+
private hasConfiguredDecorator;
|
|
841
|
+
/** True when a function is a module's own runtime entry point. */
|
|
842
|
+
private isBootstrapFunction;
|
|
843
|
+
/** True when a method is the entry point of a decorated command class. */
|
|
844
|
+
private isCommandRunnerMethod;
|
|
845
|
+
/**
|
|
846
|
+
* Every configuration whose declared addresses a run must resolve, paired
|
|
847
|
+
* with the project that wrote it.
|
|
848
|
+
*
|
|
849
|
+
* The workspace configuration is one of them and has no project to name, so
|
|
850
|
+
* it is listed once under `undefined` rather than repeated for each project
|
|
851
|
+
* that inherits it — which would resolve one address as many times as there
|
|
852
|
+
* are projects, and report one mistake as several.
|
|
853
|
+
*/
|
|
854
|
+
private listDeclaringConfigurations;
|
|
855
|
+
/** Roots whatever neither a rule nor a declaration claimed, and nothing calls. */
|
|
856
|
+
private promoteOrphans;
|
|
857
|
+
/** Reads the names of the decorators applied to a node. */
|
|
858
|
+
private readDecoratorNames;
|
|
859
|
+
/**
|
|
860
|
+
* The rules the project owning a callable is judged by.
|
|
861
|
+
*
|
|
862
|
+
* Positional rather than an options object, unlike almost everything else
|
|
863
|
+
* here: it is asked once per callable in each of two passes, and building an
|
|
864
|
+
* argument object for every one of them is thousands of allocations to say
|
|
865
|
+
* what two names already say.
|
|
866
|
+
*/
|
|
867
|
+
private readRules;
|
|
868
|
+
/**
|
|
869
|
+
* Roots one declared address, or reports why it named no single callable.
|
|
870
|
+
*
|
|
871
|
+
* Resolved through the service the `depth` and `breadth` commands use, so
|
|
872
|
+
* the `:<line>` disambiguator and every refusal behave here exactly as they
|
|
873
|
+
* do at a prompt. An address landing on a callable something already
|
|
874
|
+
* claimed adds no second root: the two are one callable under two
|
|
875
|
+
* spellings, and roots are deduplicated by the callable, never by the text.
|
|
876
|
+
*/
|
|
877
|
+
private rootDeclaredAddress;
|
|
878
|
+
/** Roots every declared address, collecting the ones that resolved to nothing. */
|
|
879
|
+
private rootDeclaredAddresses;
|
|
880
|
+
/** Reads one configuration's entry-point rules into what classification needs. */
|
|
881
|
+
private toRules;
|
|
882
|
+
/**
|
|
883
|
+
* Resolves every root a run will measure depth from.
|
|
884
|
+
*
|
|
885
|
+
* Three passes, in this order. The rules run first, so a callable one of
|
|
886
|
+
* them already rooted keeps the kind saying *why* something calls it.
|
|
887
|
+
* Declared addresses run next, rooting whatever the rules did not reach.
|
|
888
|
+
* Orphan promotion runs last, over what neither claimed.
|
|
889
|
+
*
|
|
890
|
+
* Every pass reads the rules of the project owning the callable, so a
|
|
891
|
+
* package's own entry points hold wherever the run started from — a leaf
|
|
892
|
+
* pulled in through another project's dependency closure is judged by its
|
|
893
|
+
* own configuration, not by whoever reached it.
|
|
894
|
+
*/
|
|
895
|
+
resolve(args: ResolveEntriesArguments): EntryPointCollection;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** The roots a run will measure depth from. */
|
|
899
|
+
export declare interface EntryPointCollection {
|
|
900
|
+
readonly entryPoints: readonly EntryPoint[];
|
|
901
|
+
/**
|
|
902
|
+
* Every declared address that named no callable, or more than one.
|
|
903
|
+
*
|
|
904
|
+
* Returned rather than logged or skipped: a declared address that stops
|
|
905
|
+
* resolving is a root silently leaving the measurement, which lowers the
|
|
906
|
+
* project's depth and loosens its gate with nothing in the output to say so.
|
|
907
|
+
* The caller is what turns these into a refusal.
|
|
908
|
+
*/
|
|
909
|
+
readonly unresolvedAddresses: readonly UnresolvedEntryPointAddress[];
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Directory names a project scan never descends into.
|
|
914
|
+
*
|
|
915
|
+
* Each one either holds no source of its own (`node_modules`, dependency and
|
|
916
|
+
* build caches) or is generated output (`dist`, `build`, `coverage`) — walking
|
|
917
|
+
* into any of them either finds nothing or finds a `tsconfig.json` that
|
|
918
|
+
* belongs to a dependency, not a project this run should trace.
|
|
919
|
+
*/
|
|
920
|
+
export declare const EXCLUDED_SCAN_DIRECTORY_NAMES: readonly ["node_modules", ".git", ".nx", ".conformetry", "dist", "build", "coverage", "out"];
|
|
921
|
+
|
|
922
|
+
/** A call that left the traced code: no edge, and no gap to report either. */
|
|
923
|
+
export declare const EXTERNAL_CALL: ResolvedCallSite;
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Decides whether a declaration lives outside the code being traced.
|
|
927
|
+
*
|
|
928
|
+
* This is the filter the whole tool rests on. Most call sites in any real file
|
|
929
|
+
* resolve into `lib.es5.d.ts` or a dependency; without this, every `.map()` and
|
|
930
|
+
* `.trim()` becomes a frame, and the deepest stack in the repository turns out
|
|
931
|
+
* to be somewhere inside a standard-library type declaration.
|
|
932
|
+
*
|
|
933
|
+
* The verdict is memoized per source file rather than per call site because it
|
|
934
|
+
* is asked tens of thousands of times per run and the answer only ever depends
|
|
935
|
+
* on the file.
|
|
936
|
+
*/
|
|
937
|
+
export declare class ExternalService {
|
|
938
|
+
constructor();
|
|
939
|
+
private ownedFilePaths;
|
|
940
|
+
private readonly verdicts;
|
|
941
|
+
private workspaceRoot;
|
|
942
|
+
/** Works out whether a file is outside the traced set. */
|
|
943
|
+
private computeVerdict;
|
|
944
|
+
/** Points the predicate at one run's workspace and owned-file set. */
|
|
945
|
+
configure(args: {
|
|
946
|
+
ownedFilePaths: ReadonlySet<string>;
|
|
947
|
+
workspaceRoot: string;
|
|
948
|
+
}): void;
|
|
949
|
+
/** True when a declaration's file is not part of the traced code. */
|
|
950
|
+
isExternal(sourceFile: default_2.SourceFile): boolean;
|
|
951
|
+
/** True when a file is one this run walks declarations in. */
|
|
952
|
+
isOwned(realPath: string): boolean;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/** Decides whether a file is traced. */
|
|
956
|
+
export declare interface FileFilter {
|
|
957
|
+
/** True when the file should be left out of the graph. */
|
|
958
|
+
readonly isExcluded: (workspaceRelativePath: string) => boolean;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Decides which files a run leaves out of the graph.
|
|
963
|
+
*
|
|
964
|
+
* Its own service rather than more of `WorkspaceService`, because a run builds
|
|
965
|
+
* two filters at two different moments and only one of them can exist before
|
|
966
|
+
* the projects are known. The run's own exclusions are settled from the file
|
|
967
|
+
* the run was pointed at, so they are what project discovery is judged by; a
|
|
968
|
+
* project's own exclusions are written in a file sitting at a project root, so
|
|
969
|
+
* nothing can read them until that root has been discovered. Layering the two
|
|
970
|
+
* is this service's whole subject.
|
|
971
|
+
*/
|
|
972
|
+
export declare class FileFilterService {
|
|
973
|
+
private readonly workspaceService;
|
|
974
|
+
private readonly logger;
|
|
975
|
+
constructor(workspaceService: WorkspaceService, logger: LoggerService);
|
|
976
|
+
/**
|
|
977
|
+
* True when the project owning a file excludes it with a glob of its own.
|
|
978
|
+
*
|
|
979
|
+
* The project that *owns* the file rather than any project containing it, so
|
|
980
|
+
* a project nested under another answers for its own files: a parent's globs
|
|
981
|
+
* stop at the nested root exactly the way every other per-project answer
|
|
982
|
+
* does. `WorkspaceService.resolveOwningProject` is asked rather than a second
|
|
983
|
+
* containment rule written here, because a file with two answers about which
|
|
984
|
+
* project it belongs to is worse than a file with none.
|
|
985
|
+
*
|
|
986
|
+
* The glob is matched against the path *relative to the owning project's
|
|
987
|
+
* root*, which is what keeps a project's globs inside that project. See
|
|
988
|
+
* `buildProjectFileFilter` for why that anchoring rather than the run's.
|
|
989
|
+
*/
|
|
990
|
+
private isExcludedByOwningProject;
|
|
991
|
+
/**
|
|
992
|
+
* Asks git which tracked files an ignore file excludes.
|
|
993
|
+
*
|
|
994
|
+
* Delegating to git rather than reimplementing gitignore matching is what
|
|
995
|
+
* makes `.callidescopeignore` behave the way its syntax promises. The
|
|
996
|
+
* argument vector form of `execFileSync` keeps a configured path out of a
|
|
997
|
+
* shell.
|
|
998
|
+
*/
|
|
999
|
+
private listIgnoredFiles;
|
|
1000
|
+
/**
|
|
1001
|
+
* Builds the predicate deciding which files stay out of the graph.
|
|
1002
|
+
*
|
|
1003
|
+
* Exclusion globs are matched with Node's own `path.matchesGlob` rather than
|
|
1004
|
+
* a dependency, and gitignore-syntax files are resolved through git itself.
|
|
1005
|
+
*
|
|
1006
|
+
* This is the *run's* filter and nothing else: its globs come from the file
|
|
1007
|
+
* the run was pointed at, and they are written workspace-relative because
|
|
1008
|
+
* that file describes a whole workspace. It is what project discovery is
|
|
1009
|
+
* judged by, which is the reason the per-project layer is a second call
|
|
1010
|
+
* rather than another argument here — a project's globs cannot be read until
|
|
1011
|
+
* the project has been discovered, and discovery is what this filter decides.
|
|
1012
|
+
*/
|
|
1013
|
+
buildFileFilter(args: BuildExclusionsArguments): FileFilter;
|
|
1014
|
+
/**
|
|
1015
|
+
* Layers every project's own exclusion globs over the run's filter.
|
|
1016
|
+
*
|
|
1017
|
+
* **A project's globs are anchored to that project's root**, never to the
|
|
1018
|
+
* workspace: `exclude: ["*.generated.ts"]` in `packages/thing`'s own
|
|
1019
|
+
* configuration names `packages/thing/*.generated.ts` and can name nothing
|
|
1020
|
+
* else. Three reasons, in the order they matter:
|
|
1021
|
+
*
|
|
1022
|
+
* - It is what makes "a project excludes its own files" true by
|
|
1023
|
+
* construction rather than by a rule somebody has to enforce. A
|
|
1024
|
+
* project-relative glob is only ever matched against paths under that
|
|
1025
|
+
* project's root, so there is no spelling of one that reaches a sibling.
|
|
1026
|
+
* - A file at a project root describes that project, and every other file
|
|
1027
|
+
* that sits there is already read that way — a `tsconfig.json`'s own
|
|
1028
|
+
* `include` and `exclude` are project-relative, and so is the path in a
|
|
1029
|
+
* `package.json`. A field that looked identical to the workspace file's
|
|
1030
|
+
* and quietly meant something narrower is the trap this avoids.
|
|
1031
|
+
* - The run's own `exclude` keeps its workspace-relative meaning untouched.
|
|
1032
|
+
* One field name read two ways in one file would be indefensible; one
|
|
1033
|
+
* read differently in two files, each way matching what its file is
|
|
1034
|
+
* about, is the distinction the two files already are.
|
|
1035
|
+
*
|
|
1036
|
+
* The run's filter is layered *under*, never replaced: a project cannot
|
|
1037
|
+
* un-exclude what the run excluded, which keeps `exclude` and `excludeFrom`
|
|
1038
|
+
* in the workspace file exactly as authoritative as they were.
|
|
1039
|
+
*
|
|
1040
|
+
* A run in which no project declared anything gets its own filter handed
|
|
1041
|
+
* straight back, so the ownership lookup this otherwise does per file costs
|
|
1042
|
+
* nothing at all in the workspaces — every one of them today — where no
|
|
1043
|
+
* project excludes anything.
|
|
1044
|
+
*/
|
|
1045
|
+
buildProjectFileFilter(args: BuildProjectFileFilterArguments): FileFilter;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Builds the call graph, its cycle condensation, and its depth measurement.
|
|
1050
|
+
*
|
|
1051
|
+
* Groups the four collaborators a full call-graph assembly needs behind one
|
|
1052
|
+
* call, so a consumer that wants "the graph and everything derived from it"
|
|
1053
|
+
* does not have to know the assembly order or re-wire them itself.
|
|
1054
|
+
*/
|
|
1055
|
+
export declare class GraphAssemblyService {
|
|
1056
|
+
private readonly breadthService;
|
|
1057
|
+
private readonly componentsService;
|
|
1058
|
+
private readonly depthService;
|
|
1059
|
+
private readonly edgesService;
|
|
1060
|
+
private readonly graphService;
|
|
1061
|
+
constructor(breadthService: BreadthService, componentsService: ComponentsService, depthService: GraphDepthService, edgesService: EdgesService, graphService: GraphService);
|
|
1062
|
+
/** Builds the call graph and everything derived from it. */
|
|
1063
|
+
assemble(args: AssembleGraphArguments): AssembledGraph;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Measures the longest call stack below every component.
|
|
1068
|
+
*
|
|
1069
|
+
* The traversal is iterative and post-order. Because it runs on the
|
|
1070
|
+
* condensation, which is acyclic, a memo entry is final once written — there is
|
|
1071
|
+
* no path along which a node could be revisited and get a different answer.
|
|
1072
|
+
*/
|
|
1073
|
+
export declare class GraphDepthService {
|
|
1074
|
+
constructor();
|
|
1075
|
+
/** Folds every successor's result into one component's own. */
|
|
1076
|
+
private combine;
|
|
1077
|
+
/** Picks the deepest successor of one component out of the memo. */
|
|
1078
|
+
private foldSuccessors;
|
|
1079
|
+
/** True when any member of a component holds an unfollowable call. */
|
|
1080
|
+
private hasUnresolved;
|
|
1081
|
+
/** Measures every component, deepest-first, in one iterative pass. */
|
|
1082
|
+
measure(args: MeasureDepthArguments): DepthMeasurement;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Provides graph assembly, cycle condensation, and depth and breadth measurement.
|
|
1087
|
+
*/
|
|
1088
|
+
export declare class GraphModule {
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Assembles the resolved edges into a graph indexed in both directions.
|
|
1093
|
+
*
|
|
1094
|
+
* Callers are indexed as well as callees because addressing one callable asks
|
|
1095
|
+
* who calls it as often as it asks what it calls, and answering that from an
|
|
1096
|
+
* index costs nothing once the edges exist.
|
|
1097
|
+
*/
|
|
1098
|
+
export declare class GraphService {
|
|
1099
|
+
constructor();
|
|
1100
|
+
/** Appends a value to the list stored under a key. */
|
|
1101
|
+
private append;
|
|
1102
|
+
/** Builds the two-way index over one run's edges. */
|
|
1103
|
+
assemble(collection: EdgeCollection): CallGraph;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/** What resolving an interface member to implementations produced. */
|
|
1107
|
+
export declare interface ImplementationLookup {
|
|
1108
|
+
/** Concrete member declarations, empty when none could be resolved. */
|
|
1109
|
+
readonly declarations: readonly default_2.Declaration[];
|
|
1110
|
+
/** True when the candidate set was larger than the cap. */
|
|
1111
|
+
readonly exceededCandidateLimit: boolean;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Low link a node starts from when its entry is somehow missing.
|
|
1116
|
+
*
|
|
1117
|
+
* Never reached by the traversal — every node is opened before it is lifted —
|
|
1118
|
+
* but `Map.get` reads as possibly undefined, and zero is the value an unopened
|
|
1119
|
+
* node would have had anyway.
|
|
1120
|
+
*/
|
|
1121
|
+
export declare const INITIAL_LOW_LINK = 0;
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Methods a framework calls on a class it manages.
|
|
1125
|
+
*
|
|
1126
|
+
* Nothing in a repository calls these, so without naming them every one would
|
|
1127
|
+
* be promoted as an orphan and reported with an entry-point kind that says
|
|
1128
|
+
* nothing about why it runs.
|
|
1129
|
+
*/
|
|
1130
|
+
export declare const LIFECYCLE_METHOD_NAMES: Set<string>;
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Structured values that belong beside a log line rather than inside it.
|
|
1134
|
+
*
|
|
1135
|
+
* Counts, percentages, and durations are the values that change on every
|
|
1136
|
+
* occurrence, so they are carried as fields: the message stays constant and
|
|
1137
|
+
* groupable in telemetry, and the numbers stay queryable instead of having to
|
|
1138
|
+
* be parsed back out of prose.
|
|
1139
|
+
*
|
|
1140
|
+
* The named members are the recurring ones; the index signature keeps the
|
|
1141
|
+
* argument open for whatever a given call site needs to attach.
|
|
1142
|
+
*/
|
|
1143
|
+
declare interface LogData {
|
|
1144
|
+
[key: string]: unknown;
|
|
1145
|
+
/** How many things the operation handled. */
|
|
1146
|
+
count?: number;
|
|
1147
|
+
/** Wall-clock milliseconds the operation took. */
|
|
1148
|
+
durationMs?: number;
|
|
1149
|
+
/** Completion between 0 and 100. */
|
|
1150
|
+
percent?: number;
|
|
1151
|
+
/** How many things the operation set out to handle. */
|
|
1152
|
+
total?: number;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Transient-scoped logger so each injecting class gets its own instance.
|
|
1157
|
+
* Each consumer calls `setContext(ClassName.name)` to tag every log line
|
|
1158
|
+
* with the originating class. Backed by pino for structured JSON output in
|
|
1159
|
+
* production and human-readable pretty-print in development.
|
|
1160
|
+
*
|
|
1161
|
+
* Messages follow one grammar: an emoji naming the subject, a verb in present
|
|
1162
|
+
* progressive or past tense, then the object. Values that vary per call —
|
|
1163
|
+
* counts, percentages, durations — go in the `data` argument rather than the
|
|
1164
|
+
* message, so the message stays constant enough for telemetry to group on.
|
|
1165
|
+
*
|
|
1166
|
+
* ```ts
|
|
1167
|
+
* this.logger.info("📥 Downloading CSEL sources", undefined, { total: 428 });
|
|
1168
|
+
* this.logger.info("📥 Downloaded CSEL sources", undefined, { count: 412 });
|
|
1169
|
+
* ```
|
|
1170
|
+
*/
|
|
1171
|
+
declare @Injectable({ scope: Scope.TRANSIENT })
|
|
1172
|
+
class LoggerService extends ConsoleLogger {
|
|
1173
|
+
// 🏗 Dependency Injection
|
|
1174
|
+
|
|
1175
|
+
constructor() {
|
|
1176
|
+
super();
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// 🔐 Private Fields
|
|
1180
|
+
|
|
1181
|
+
private static readonly isProduction =
|
|
1182
|
+
process.env["NODE_ENV"] === "production";
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Built on first use, not when this file is evaluated.
|
|
1186
|
+
*
|
|
1187
|
+
* A destination fixed at import time could only ever be chosen by this
|
|
1188
|
+
* package, since every consumer's own code runs after its imports.
|
|
1189
|
+
*/
|
|
1190
|
+
private static rootLogger: pino.Logger | undefined;
|
|
1191
|
+
|
|
1192
|
+
/** Whether lines go to standard error instead of standard output. */
|
|
1193
|
+
private static writesToStandardError = false;
|
|
1194
|
+
|
|
1195
|
+
private child: pino.Logger = LoggerService.root;
|
|
1196
|
+
|
|
1197
|
+
// 🔑 Public Fields
|
|
1198
|
+
|
|
1199
|
+
// 🔏 Private Methods
|
|
1200
|
+
|
|
1201
|
+
/** Build the pino instance for production or local development output. */
|
|
1202
|
+
private static createRootLogger(): pino.Logger {
|
|
1203
|
+
const level = process.env["LOG_LEVEL"] ?? "info";
|
|
1204
|
+
|
|
1205
|
+
if (LoggerService.isProduction) {
|
|
1206
|
+
return LoggerService.writesToStandardError
|
|
1207
|
+
? pino({ level }, pino.destination(STANDARD_ERROR_DESCRIPTOR))
|
|
1208
|
+
: pino({ level });
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return pino({
|
|
1212
|
+
level,
|
|
1213
|
+
transport: {
|
|
1214
|
+
options: {
|
|
1215
|
+
colorize: true,
|
|
1216
|
+
destination: LoggerService.writesToStandardError
|
|
1217
|
+
? STANDARD_ERROR_DESCRIPTOR
|
|
1218
|
+
: STANDARD_OUTPUT_DESCRIPTOR,
|
|
1219
|
+
// The emoji is a field, not part of the message, so the console can
|
|
1220
|
+
// show it while telemetry stores unadorned prose. `ignore` then keeps
|
|
1221
|
+
// it from being printed a second time in the trailing object.
|
|
1222
|
+
ignore: "pid,hostname,emoji",
|
|
1223
|
+
messageFormat: "{emoji} {msg}",
|
|
1224
|
+
singleLine: true,
|
|
1225
|
+
},
|
|
1226
|
+
target: "pino-pretty",
|
|
1227
|
+
},
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Sends every subsequent line to standard error instead of standard output.
|
|
1233
|
+
*
|
|
1234
|
+
* For a command-line application whose standard output *is* its result. A log
|
|
1235
|
+
* line sharing that stream is not a diagnostic beside the data, it is a
|
|
1236
|
+
* corruption of it. Call it before anything logs — the first statement of the
|
|
1237
|
+
* application's bootstrap.
|
|
1238
|
+
*
|
|
1239
|
+
* A call after the first line warns and changes nothing: the destination is
|
|
1240
|
+
* fixed when the pino instance is built, and tearing down a transport
|
|
1241
|
+
* somebody is writing through would be worse than refusing. The warning is
|
|
1242
|
+
* the point — silently leaving the lines on standard output is how a caller
|
|
1243
|
+
* would ship a corrupted pipe without ever being told.
|
|
1244
|
+
*/
|
|
1245
|
+
static logToStandardError(): void {
|
|
1246
|
+
if (LoggerService.rootLogger !== undefined) {
|
|
1247
|
+
process.emitWarning(
|
|
1248
|
+
"LoggerService.logToStandardError() was called after the first log line, so log lines still go to standard output and anything piping that stream will read them as data. Call it as the first statement of the application's bootstrap.",
|
|
1249
|
+
);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
LoggerService.writesToStandardError = true;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Fails a malformed message in development, and never in production.
|
|
1258
|
+
*
|
|
1259
|
+
* A logger that throws in production turns an observability call into an
|
|
1260
|
+
* outage, so the check runs only where a developer is present to fix it.
|
|
1261
|
+
*/
|
|
1262
|
+
private assertConventionalMessage(args: {
|
|
1263
|
+
context: string | undefined;
|
|
1264
|
+
parsed: ParsedLogMessage;
|
|
1265
|
+
}): void {
|
|
1266
|
+
if (
|
|
1267
|
+
LoggerService.isProduction ||
|
|
1268
|
+
this.shouldSkipConventionalMessageValidation(args.context)
|
|
1269
|
+
) {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const violation = this.getConventionalMessageViolation(args.parsed);
|
|
1274
|
+
|
|
1275
|
+
if (violation !== undefined) {
|
|
1276
|
+
throw new Error(violation);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/** Assembles the object pino merges into the line. */
|
|
1281
|
+
private buildBindings(args: {
|
|
1282
|
+
context: string | undefined;
|
|
1283
|
+
data: LogData | undefined;
|
|
1284
|
+
parsed: ParsedLogMessage;
|
|
1285
|
+
}): Record<string, unknown> {
|
|
1286
|
+
this.assertConventionalMessage({
|
|
1287
|
+
context: args.context,
|
|
1288
|
+
parsed: args.parsed,
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
return {
|
|
1292
|
+
...args.data,
|
|
1293
|
+
context: args.context,
|
|
1294
|
+
// Telemetry gets prose; only the console-bound transport reads this.
|
|
1295
|
+
...(LoggerService.isProduction ? {} : { emoji: args.parsed.emoji }),
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/** Returns a human-readable explanation when the message format is invalid. */
|
|
1300
|
+
private getConventionalMessageViolation(
|
|
1301
|
+
parsed: ParsedLogMessage,
|
|
1302
|
+
): string | undefined {
|
|
1303
|
+
const emoji = parsed.emoji;
|
|
1304
|
+
const text = parsed.text;
|
|
1305
|
+
|
|
1306
|
+
if (emoji === undefined) {
|
|
1307
|
+
return `Log message must start with an emoji naming its subject, then a verb: "${text}"`;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const firstWord = FIRST_WORD_PATTERN.exec(text)?.[1];
|
|
1311
|
+
|
|
1312
|
+
if (firstWord === undefined || !this.isConventionalVerb(firstWord)) {
|
|
1313
|
+
return `Log message must begin with a verb in present progressive or past tense, got "${firstWord ?? ""}": "${emoji} ${text}"`;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
return undefined;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* Whether a word is a verb in one of the two tenses the convention allows.
|
|
1321
|
+
*
|
|
1322
|
+
* Present progressive means the operation is under way; past means it
|
|
1323
|
+
* finished. Regular morphology covers both, so a new verb needs no
|
|
1324
|
+
* registration anywhere — only irregular pasts are enumerated.
|
|
1325
|
+
*/
|
|
1326
|
+
private isConventionalVerb(word: string): boolean {
|
|
1327
|
+
const lowercased = word.toLowerCase();
|
|
1328
|
+
|
|
1329
|
+
return (
|
|
1330
|
+
lowercased.endsWith("ing") ||
|
|
1331
|
+
lowercased.endsWith("ed") ||
|
|
1332
|
+
IRREGULAR_PAST_VERBS.has(lowercased)
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/** Splits a leading emoji off a message, leaving prose behind. */
|
|
1337
|
+
private parseMessage(message: unknown): ParsedLogMessage {
|
|
1338
|
+
const text = String(message);
|
|
1339
|
+
const match = LEADING_EMOJI_PATTERN.exec(text);
|
|
1340
|
+
const emoji = match?.[1];
|
|
1341
|
+
|
|
1342
|
+
return emoji === undefined
|
|
1343
|
+
? { emoji: undefined, text }
|
|
1344
|
+
: { emoji, text: text.slice(match?.[0].length) };
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/** Whether a context is intentionally exempt from the validation rule. */
|
|
1348
|
+
private shouldSkipConventionalMessageValidation(
|
|
1349
|
+
context: string | undefined,
|
|
1350
|
+
): boolean {
|
|
1351
|
+
return context !== undefined && UNVALIDATED_LOG_CONTEXTS.has(context);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// 🌎 Public Methods
|
|
1355
|
+
|
|
1356
|
+
/** The pino instance every logger's child is taken from. */
|
|
1357
|
+
private static get root(): pino.Logger {
|
|
1358
|
+
LoggerService.rootLogger ??= LoggerService.createRootLogger();
|
|
1359
|
+
|
|
1360
|
+
return LoggerService.rootLogger;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/** Normalizes unknown errors into a stable message and timestamped log line. */
|
|
1364
|
+
buildErrorLogEntry(
|
|
1365
|
+
context: string,
|
|
1366
|
+
error: unknown,
|
|
1367
|
+
): { errorMessage: string; logLine: string } {
|
|
1368
|
+
const errorMessage =
|
|
1369
|
+
error instanceof Error ? error.stack || error.message : String(error);
|
|
1370
|
+
|
|
1371
|
+
return {
|
|
1372
|
+
errorMessage,
|
|
1373
|
+
logLine: `[${new Date().toISOString()}] ${context}: ${errorMessage}\n`,
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/** Builds a timestamped output log file path and ensures the output directory exists. */
|
|
1378
|
+
createTimestampedOutputLogFilePath(filePrefix: string): string {
|
|
1379
|
+
const outputDirectory = path.join(process.cwd(), "output");
|
|
1380
|
+
if (!existsSync(outputDirectory)) {
|
|
1381
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
return path.join(
|
|
1385
|
+
outputDirectory,
|
|
1386
|
+
`${filePrefix}-${new Date().toISOString().replaceAll(/[:.]/g, "-")}.log`,
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/** Logs a debug message at the `debug` level. */
|
|
1391
|
+
override debug(message: unknown, context?: string, data?: LogData): void {
|
|
1392
|
+
const parsed = this.parseMessage(message);
|
|
1393
|
+
this.child.debug(
|
|
1394
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
1395
|
+
parsed.text,
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* Logs an error message at the `error` level, optionally including a stack trace.
|
|
1401
|
+
*
|
|
1402
|
+
* `ConsoleLogger.error` spends a third slot on a context string that the
|
|
1403
|
+
* other levels do not have, so this one accepts either: a string keeps
|
|
1404
|
+
* NestJS's meaning, an object is structured data like everywhere else.
|
|
1405
|
+
*/
|
|
1406
|
+
override error(
|
|
1407
|
+
message: unknown,
|
|
1408
|
+
stackOrContext?: string,
|
|
1409
|
+
contextOrData?: LogData | string,
|
|
1410
|
+
): void {
|
|
1411
|
+
const parsed = this.parseMessage(message);
|
|
1412
|
+
const data = typeof contextOrData === "object" ? contextOrData : undefined;
|
|
1413
|
+
const context =
|
|
1414
|
+
typeof contextOrData === "string" ? contextOrData : this.context;
|
|
1415
|
+
|
|
1416
|
+
this.child.error(
|
|
1417
|
+
{
|
|
1418
|
+
...this.buildBindings({ context, data, parsed }),
|
|
1419
|
+
stack: stackOrContext,
|
|
1420
|
+
},
|
|
1421
|
+
parsed.text,
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/** Logs an informational message at the `info` level. */
|
|
1426
|
+
info(message: unknown, context?: string, data?: LogData): void {
|
|
1427
|
+
const parsed = this.parseMessage(message);
|
|
1428
|
+
this.child.info(
|
|
1429
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
1430
|
+
parsed.text,
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
/**
|
|
1435
|
+
* Logs an informational message at the `info` level.
|
|
1436
|
+
*
|
|
1437
|
+
* NestJS and `nest-commander` call this method directly as part of the
|
|
1438
|
+
* framework's own `LoggerService` contract, so it must keep working
|
|
1439
|
+
* exactly as before. Application code should call `info` instead — the
|
|
1440
|
+
* same behavior under a name that says what level it logs at.
|
|
1441
|
+
*/
|
|
1442
|
+
override log(message: unknown, context?: string, data?: LogData): void {
|
|
1443
|
+
this.info(message, context, data);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/** Sets the context label included in every subsequent log line. */
|
|
1447
|
+
override setContext(context: string): void {
|
|
1448
|
+
super.setContext(context);
|
|
1449
|
+
this.child = LoggerService.root.child({ context });
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/** Logs a verbose message at the `trace` level. */
|
|
1453
|
+
override verbose(message: unknown, context?: string, data?: LogData): void {
|
|
1454
|
+
const parsed = this.parseMessage(message);
|
|
1455
|
+
this.child.trace(
|
|
1456
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
1457
|
+
parsed.text,
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/** Logs a warning message at the `warn` level. */
|
|
1462
|
+
override warn(message: unknown, context?: string, data?: LogData): void {
|
|
1463
|
+
const parsed = this.parseMessage(message);
|
|
1464
|
+
this.child.warn(
|
|
1465
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
1466
|
+
parsed.text,
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* How many paths `AddressDepthService` enumerates in one direction before it
|
|
1473
|
+
* stops.
|
|
1474
|
+
*
|
|
1475
|
+
* A single deepest path, which is all `PathsService` ever materializes, is
|
|
1476
|
+
* bounded by construction. Enumerating every path is not: a callable reached
|
|
1477
|
+
* from a dozen places whose callees fan out just as wide multiplies those
|
|
1478
|
+
* branches together, so a cap is what keeps a widely-called utility from
|
|
1479
|
+
* making the walk run away instead of returning.
|
|
1480
|
+
*/
|
|
1481
|
+
export declare const MAXIMUM_CALL_ADDRESS_STACKS = 200;
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Concrete implementations one interface member may resolve to before the call
|
|
1485
|
+
* is recorded as unresolved instead.
|
|
1486
|
+
*
|
|
1487
|
+
* Not a limit on what a run judges — it is the threshold at which structural
|
|
1488
|
+
* interface resolution stops guessing. Where an interface member has no
|
|
1489
|
+
* nominal implementers, the type checker is asked which classes are assignable
|
|
1490
|
+
* to it, and a member named `run`, `emit`, or `sync` structurally matches
|
|
1491
|
+
* dozens of unrelated classes in a real workspace. Expanding all of them
|
|
1492
|
+
* manufactures call stacks no execution ever takes, so past the cap the whole
|
|
1493
|
+
* expansion is dropped rather than narrowed to a favorite.
|
|
1494
|
+
*
|
|
1495
|
+
* A constant rather than a configuration field. Removing it outright would
|
|
1496
|
+
* follow every candidate and move every depth measurement upward
|
|
1497
|
+
* unpredictably; making it configurable asks every project to hold an opinion
|
|
1498
|
+
* about a noise control none of them varies. Eight is the number every
|
|
1499
|
+
* configuration in this repository already used.
|
|
1500
|
+
*/
|
|
1501
|
+
export declare const MAXIMUM_IMPLEMENTATION_CANDIDATES = 8;
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* Arguments for measuring breadth across the graph.
|
|
1505
|
+
*
|
|
1506
|
+
* No condensation is needed: unlike depth, a callable's breadth is well
|
|
1507
|
+
* defined even when it sits in a cycle, so breadth reads `graph` directly.
|
|
1508
|
+
*/
|
|
1509
|
+
export declare interface MeasureBreadthArguments {
|
|
1510
|
+
readonly callableIds: readonly CallableId[];
|
|
1511
|
+
readonly graph: CallGraph;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/** Arguments for measuring depth across the graph. */
|
|
1515
|
+
export declare interface MeasureDepthArguments {
|
|
1516
|
+
readonly condensed: CondensedGraph;
|
|
1517
|
+
readonly graph: CallGraph;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/** An interface member nothing in the traced code implements. */
|
|
1521
|
+
export declare const NO_IMPLEMENTATION_CALL: ResolvedCallSite;
|
|
1522
|
+
|
|
1523
|
+
/** A call whose callee has no symbol the checker will name. */
|
|
1524
|
+
export declare const NO_SYMBOL_CALL: ResolvedCallSite;
|
|
1525
|
+
|
|
1526
|
+
/** A message split into the emoji the console shows and the prose telemetry stores. */
|
|
1527
|
+
declare interface ParsedLogMessage {
|
|
1528
|
+
emoji: string | undefined;
|
|
1529
|
+
text: string;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Rebuilds the single deepest call stack below an entry point.
|
|
1534
|
+
*
|
|
1535
|
+
* Only one path per entry point is ever materialized, by following the
|
|
1536
|
+
* successor each component already recorded as its deepest. Enumerating every
|
|
1537
|
+
* root-to-leaf path instead would be the same answer arrived at by walking a
|
|
1538
|
+
* number of paths that grows with the product of the branching factors, and
|
|
1539
|
+
* then discarding all but one of them.
|
|
1540
|
+
*/
|
|
1541
|
+
export declare class PathsService {
|
|
1542
|
+
private readonly documentationService;
|
|
1543
|
+
private readonly signaturesService;
|
|
1544
|
+
constructor(documentationService: DocumentationService, signaturesService: SignaturesService);
|
|
1545
|
+
/**
|
|
1546
|
+
* Orders one component's members so the entered one comes first.
|
|
1547
|
+
*
|
|
1548
|
+
* Within a cycle there is no single true order, but starting at the member
|
|
1549
|
+
* the caller actually reached makes the printed stack match how execution
|
|
1550
|
+
* got there.
|
|
1551
|
+
*/
|
|
1552
|
+
private orderMembers;
|
|
1553
|
+
/** Walks the deepest chain below one callable and returns its frames. */
|
|
1554
|
+
buildDeepestPath(args: {
|
|
1555
|
+
callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
1556
|
+
condensed: CondensedGraph;
|
|
1557
|
+
entryPointId: CallableId;
|
|
1558
|
+
measurement: DepthMeasurement;
|
|
1559
|
+
}): StackFrame[];
|
|
1560
|
+
/**
|
|
1561
|
+
* Turns one callable into a frame a report can print.
|
|
1562
|
+
*
|
|
1563
|
+
* Public so any traversal over the same `DiscoveredCallable` map — not only
|
|
1564
|
+
* this service's own deepest-path walk — can render a frame the same way,
|
|
1565
|
+
* rather than reading documentation and a signature by a second route.
|
|
1566
|
+
*/
|
|
1567
|
+
buildFrame(args: {
|
|
1568
|
+
callable: DiscoveredCallable;
|
|
1569
|
+
isCycle: boolean;
|
|
1570
|
+
}): StackFrame;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
/**
|
|
1574
|
+
* Raised when a project's `tsconfig.json` cannot be read.
|
|
1575
|
+
*
|
|
1576
|
+
* Fatal to the whole run rather than skipped, and deliberately so. A host
|
|
1577
|
+
* writes its report before it weighs its findings, so a run that stepped over
|
|
1578
|
+
* the project would publish depths measured through a graph missing it and
|
|
1579
|
+
* only then fail — and on a default branch that means committing wrong numbers
|
|
1580
|
+
* that failing afterwards does not take back. Ending the run leaves every
|
|
1581
|
+
* destination exactly as it found it.
|
|
1582
|
+
*
|
|
1583
|
+
* Raised for a directory a run was told to trace that holds no
|
|
1584
|
+
* `tsconfig.json` at all, as well as for one whose configuration will not
|
|
1585
|
+
* parse. Both are the same mistake seen from two sides — the run was pointed
|
|
1586
|
+
* at something it cannot build a program from — and both used to be stepped
|
|
1587
|
+
* over, which is how a run came to pass a gate for having traced nothing.
|
|
1588
|
+
*
|
|
1589
|
+
* A project that should not be read at all is a different question, answered
|
|
1590
|
+
* by an exclusion: `WorkspaceService.discoverProjects` drops it before its
|
|
1591
|
+
* configuration is ever opened.
|
|
1592
|
+
*/
|
|
1593
|
+
export declare class ProgramConfigurationError extends Error {
|
|
1594
|
+
constructor(args: {
|
|
1595
|
+
configurationPath: string;
|
|
1596
|
+
messages: string[];
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* Provides TypeScript programs and type checkers, one per project.
|
|
1602
|
+
*/
|
|
1603
|
+
export declare class ProgramModule {
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* Turns each project's `tsconfig.json` into a program and a type checker.
|
|
1608
|
+
*
|
|
1609
|
+
* One program per project rather than one merged program, and that is a
|
|
1610
|
+
* correctness decision rather than a performance one. The projects here
|
|
1611
|
+
* disagree about `jsx`, `lib`, `types`, and `paths`; merging their options
|
|
1612
|
+
* changes which globals exist and how modules resolve, so the checker starts
|
|
1613
|
+
* answering with different symbols and the call graph silently gains wrong
|
|
1614
|
+
* edges. Paying for twenty-odd programs buys the right answer.
|
|
1615
|
+
*/
|
|
1616
|
+
export declare class ProgramService {
|
|
1617
|
+
private readonly compilerHostService;
|
|
1618
|
+
private readonly logger;
|
|
1619
|
+
private readonly workspaceService;
|
|
1620
|
+
constructor(compilerHostService: CompilerHostService, logger: LoggerService, workspaceService: WorkspaceService);
|
|
1621
|
+
/**
|
|
1622
|
+
* Assigns each file to the program whose project root contains it.
|
|
1623
|
+
*
|
|
1624
|
+
* Projects overlap: a project that nests a second `tsconfig.json` beneath
|
|
1625
|
+
* it can list the same file as its parent, when the parent's own `include`
|
|
1626
|
+
* is broad enough to reach it too. Containment settles that overlap by the
|
|
1627
|
+
* file's location on disk rather than by which program asked first, which
|
|
1628
|
+
* is what keeps a reported depth the same however a run is scoped — a
|
|
1629
|
+
* closure that starts programs in a different order reaches the same
|
|
1630
|
+
* answer. A file none of `programs`' projects contains is left out of the
|
|
1631
|
+
* map rather than guessed at; `readOwnedPath` in `CallablesService` then
|
|
1632
|
+
* walks it through no program at all.
|
|
1633
|
+
*/
|
|
1634
|
+
private assignOwnership;
|
|
1635
|
+
/** Builds one project's program, checker, and owned-file set. */
|
|
1636
|
+
private buildProgram;
|
|
1637
|
+
/**
|
|
1638
|
+
* Reads and fully resolves one project's compiler options.
|
|
1639
|
+
*
|
|
1640
|
+
* The configuration file name is passed as the fifth argument because that is
|
|
1641
|
+
* what makes TypeScript follow the `extends` chain to the shared base config
|
|
1642
|
+
* and report diagnostics against the right file.
|
|
1643
|
+
*/
|
|
1644
|
+
private parseConfiguration;
|
|
1645
|
+
/**
|
|
1646
|
+
* Reports the workspace-relative paths one program pulled in.
|
|
1647
|
+
*
|
|
1648
|
+
* `getSourceFiles()` rather than the parsed configuration's `fileNames`,
|
|
1649
|
+
* and the difference is the whole point: `fileNames` is what a project's own
|
|
1650
|
+
* `tsconfig.json` listed, which by definition never mentions the packages it
|
|
1651
|
+
* imports. `getSourceFiles()` is what the compiler actually had to read to
|
|
1652
|
+
* type the project, so a workspace package reached through an import is in
|
|
1653
|
+
* there — and in a pnpm workspace it is reached through a symlink, which is
|
|
1654
|
+
* why every path goes through `toRealPath` before it is made relative.
|
|
1655
|
+
*
|
|
1656
|
+
* A path under `node_modules` is dropped rather than reported. It is a real
|
|
1657
|
+
* dependency rather than workspace code, and the workspace root is itself a
|
|
1658
|
+
* project whose root contains every such path — so reporting one would walk
|
|
1659
|
+
* `lib.es5.d.ts` back to the root project and pull the entire workspace into
|
|
1660
|
+
* every closure.
|
|
1661
|
+
*/
|
|
1662
|
+
private readPulledInPaths;
|
|
1663
|
+
/**
|
|
1664
|
+
* Builds a program for every project in the starting projects' dependency
|
|
1665
|
+
* closure, and decides which one owns each file.
|
|
1666
|
+
*
|
|
1667
|
+
* The apparent circularity — the closure names the projects to build, but
|
|
1668
|
+
* naming them means asking what each one pulled in, which means building it
|
|
1669
|
+
* — is only apparent. Finding project *roots* is a filesystem walk that
|
|
1670
|
+
* builds nothing; finding what a project *reaches* is what needs a program.
|
|
1671
|
+
* So the closure walk is driven from here, and the callback it asks for a
|
|
1672
|
+
* project's files is what builds that project's program. The traversal asks
|
|
1673
|
+
* exactly once per project it reaches, and the programs built along the way
|
|
1674
|
+
* are kept rather than discarded — they are precisely the set the run goes
|
|
1675
|
+
* on to trace with, so rebuilding them afterwards would build the whole
|
|
1676
|
+
* closure twice.
|
|
1677
|
+
*
|
|
1678
|
+
* A project nothing reaches is never asked about and so never built, which
|
|
1679
|
+
* is what keeps a run scoped to one package from compiling the workspace.
|
|
1680
|
+
* Neither is a project reached only as a directory of shared settings, nor
|
|
1681
|
+
* the workspace root itself — `WorkspaceService.isClosureDestination`
|
|
1682
|
+
* refuses those and holds the reasoning. An unscoped run passes every
|
|
1683
|
+
* project as a
|
|
1684
|
+
* starting project, so its closure is every project, both rules are moot,
|
|
1685
|
+
* and nothing about it changes.
|
|
1686
|
+
*
|
|
1687
|
+
* A project whose configuration cannot be parsed ends the run rather than
|
|
1688
|
+
* being stepped over — see `ProgramConfigurationError` for why a partial
|
|
1689
|
+
* graph is the worse outcome. A project that should not be read at all is
|
|
1690
|
+
* kept out by an exclusion, which `WorkspaceService.discoverProjects`
|
|
1691
|
+
* applies to both lists before this ever sees them, so an unreadable
|
|
1692
|
+
* fixture an ignore file names is not in `workspaceProjects` and no closure
|
|
1693
|
+
* can reach it.
|
|
1694
|
+
*/
|
|
1695
|
+
buildPrograms(args: BuildProgramsArguments): ProgramSet;
|
|
1696
|
+
/** Resolves a path through symlinks, which is how pnpm workspaces link. */
|
|
1697
|
+
toRealPath(filePath: string): string;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/** The programs a run built, and the ownership decisions behind them. */
|
|
1701
|
+
export declare interface ProgramSet {
|
|
1702
|
+
/** Absolute real path to the program that owns it. */
|
|
1703
|
+
readonly ownerByFilePath: ReadonlyMap<string, ProjectProgram>;
|
|
1704
|
+
readonly programs: readonly ProjectProgram[];
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
/**
|
|
1708
|
+
* The file whose presence in a directory makes it a project.
|
|
1709
|
+
*
|
|
1710
|
+
* Named once rather than spelled at each site, so the file a whole-workspace
|
|
1711
|
+
* scan looks for and the file an exclusion is judged against can never drift
|
|
1712
|
+
* apart — a project excluded by a path that is not the one discovery reads
|
|
1713
|
+
* would be excluded from nothing.
|
|
1714
|
+
*/
|
|
1715
|
+
export declare const PROJECT_CONFIGURATION_NAME = "tsconfig.json";
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* One project's program, its checker, and the files it owns.
|
|
1719
|
+
*
|
|
1720
|
+
* A file's declarations are walked exactly once, in the program that owns it.
|
|
1721
|
+
* Files pulled in as dependencies are still reachable through the checker, but
|
|
1722
|
+
* walking them here as well would double every callable in the graph.
|
|
1723
|
+
*/
|
|
1724
|
+
export declare interface ProjectProgram {
|
|
1725
|
+
readonly checker: default_2.TypeChecker;
|
|
1726
|
+
/** Absolute real paths this program is responsible for walking. */
|
|
1727
|
+
readonly ownedFilePaths: ReadonlySet<string>;
|
|
1728
|
+
readonly program: default_2.Program;
|
|
1729
|
+
readonly project: WorkspaceProject;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
/** Arguments for reading the documentation comment above a callable. */
|
|
1733
|
+
export declare interface ReadDocumentationArguments {
|
|
1734
|
+
readonly checker: default_2.TypeChecker;
|
|
1735
|
+
readonly declaration: CallableDeclaration;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/** Arguments for reading what a callable takes and returns. */
|
|
1739
|
+
export declare interface ReadSignatureArguments {
|
|
1740
|
+
readonly checker: default_2.TypeChecker;
|
|
1741
|
+
readonly declaration: CallableDeclaration;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/** Arguments for resolving one callable address string. */
|
|
1745
|
+
export declare interface ResolveAddressArguments {
|
|
1746
|
+
readonly address: string;
|
|
1747
|
+
readonly callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
1748
|
+
readonly workspaceRoot: string;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/** What resolving one call site produced. */
|
|
1752
|
+
export declare interface ResolvedCallSite {
|
|
1753
|
+
readonly declarations: readonly default_2.Declaration[];
|
|
1754
|
+
readonly reason: undefined | UnresolvedReason;
|
|
1755
|
+
readonly resolution: "alias" | "direct" | "implementation" | "super";
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/** Arguments for deciding which callables root a call stack. */
|
|
1759
|
+
export declare interface ResolveEntriesArguments {
|
|
1760
|
+
readonly callablesById: ReadonlyMap<CallableId, DiscoveredCallable>;
|
|
1761
|
+
/**
|
|
1762
|
+
* The rules a project with no configuration of its own is judged by — the
|
|
1763
|
+
* workspace configuration's.
|
|
1764
|
+
*/
|
|
1765
|
+
readonly entryPoints: ResolvedCallidescopeEntryPoints;
|
|
1766
|
+
/**
|
|
1767
|
+
* The rules a project declared for itself, keyed by project name. A project
|
|
1768
|
+
* absent here is judged by `entryPoints`.
|
|
1769
|
+
*/
|
|
1770
|
+
readonly entryPointsByProject: ReadonlyMap<string, ResolvedCallidescopeEntryPoints>;
|
|
1771
|
+
readonly graph: CallGraph;
|
|
1772
|
+
readonly workspaceRoot: string;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* Reports the workspace-relative paths one project's program pulled in.
|
|
1777
|
+
*
|
|
1778
|
+
* How that project came to have a program at all — built fresh for this
|
|
1779
|
+
* traversal, reused from an earlier one — is entirely the implementation's
|
|
1780
|
+
* concern. `walkImportedProjectClosure` reads only the paths that come back.
|
|
1781
|
+
*/
|
|
1782
|
+
declare type ResolveProjectFilesFunction = (project: WorkspaceProject) => readonly string[];
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* How TypeScript renders the types in a signature.
|
|
1786
|
+
*
|
|
1787
|
+
* Aliases defined elsewhere are kept by name rather than expanded, because
|
|
1788
|
+
* `MeasureArguments` tells a reader more in one word than the object it stands
|
|
1789
|
+
* for does in forty.
|
|
1790
|
+
*/
|
|
1791
|
+
export declare const SIGNATURE_FORMAT_FLAGS = ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Provides the parameters and return type a report prints beside a frame.
|
|
1795
|
+
*/
|
|
1796
|
+
export declare class SignaturesModule {
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* Reads what a callable takes and what it gives back.
|
|
1801
|
+
*
|
|
1802
|
+
* Resolved through the type checker rather than read off the syntax, which is
|
|
1803
|
+
* what makes it useful on the shapes the graph actually points at. A callback
|
|
1804
|
+
* passed to `map` declares no types at all and gets them from the parameter it
|
|
1805
|
+
* was passed to; a destructured parameter has no name in the source; an
|
|
1806
|
+
* inferred return type is written down nowhere.
|
|
1807
|
+
*/
|
|
1808
|
+
export declare class SignaturesService {
|
|
1809
|
+
constructor();
|
|
1810
|
+
/** Describes one parameter, including how it may be left out. */
|
|
1811
|
+
private readParameter;
|
|
1812
|
+
/** Reads the parameters and return type of a callable. */
|
|
1813
|
+
read(args: ReadSignatureArguments): CallableSignature | undefined;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Resolves one call expression to the declarations it can reach.
|
|
1818
|
+
*
|
|
1819
|
+
* The fast path is asking the checker for the symbol at the member name, which
|
|
1820
|
+
* already answers the case this tool exists for: a call on a constructor-
|
|
1821
|
+
* injected property. The parameter property carries the service's type, the
|
|
1822
|
+
* checker follows it, and no dependency-injection machinery is needed to trace
|
|
1823
|
+
* a NestJS call graph. Everything below that is the long tail.
|
|
1824
|
+
*/
|
|
1825
|
+
export declare class SymbolResolutionService {
|
|
1826
|
+
private readonly classHierarchyService;
|
|
1827
|
+
private readonly externalService;
|
|
1828
|
+
constructor(classHierarchyService: ClassesService, externalService: ExternalService);
|
|
1829
|
+
/** True when a declaration is a signature rather than an implementation. */
|
|
1830
|
+
private isAbstractMember;
|
|
1831
|
+
/**
|
|
1832
|
+
* True when a declaration is a function form that can carry a body.
|
|
1833
|
+
*
|
|
1834
|
+
* Spelled out rather than using `ts.isFunctionLike`, which also admits bare
|
|
1835
|
+
* call and construct signatures — those have no body to push a frame for, and
|
|
1836
|
+
* narrowing to them makes `.body` unreadable.
|
|
1837
|
+
*/
|
|
1838
|
+
private isBodyCarrying;
|
|
1839
|
+
/** Keeps the declarations that actually have a body to execute. */
|
|
1840
|
+
private readBodied;
|
|
1841
|
+
/** Reads the name node a call's callee is identified by. */
|
|
1842
|
+
private readCalleeName;
|
|
1843
|
+
/**
|
|
1844
|
+
* Names the type a member is declared on, and where that name is written.
|
|
1845
|
+
*
|
|
1846
|
+
* The name node comes back alongside the text because the checker needs a
|
|
1847
|
+
* location to resolve the owning symbol from, and fetching it separately
|
|
1848
|
+
* would mean checking twice that the owner exists at all.
|
|
1849
|
+
*/
|
|
1850
|
+
private readOwner;
|
|
1851
|
+
/** Names how a resolved call reached its target. */
|
|
1852
|
+
private readResolution;
|
|
1853
|
+
/**
|
|
1854
|
+
* Resolves an already-identified callee symbol to its declarations.
|
|
1855
|
+
*
|
|
1856
|
+
* Split from `resolve` so that identifying the callee and following it stay
|
|
1857
|
+
* separately readable — and separately testable, since the shapes that fail
|
|
1858
|
+
* to identify a callee are nothing like the shapes that fail to follow one.
|
|
1859
|
+
*/
|
|
1860
|
+
private resolveSymbol;
|
|
1861
|
+
/** Expands an interface or abstract member to its implementations. */
|
|
1862
|
+
private resolveThroughHierarchy;
|
|
1863
|
+
/** Unwraps an import alias to the symbol it actually names. */
|
|
1864
|
+
private unwrapAlias;
|
|
1865
|
+
/** Resolves a call expression to every declaration it can reach. */
|
|
1866
|
+
resolve(args: {
|
|
1867
|
+
checker: default_2.TypeChecker;
|
|
1868
|
+
expression: default_2.CallExpression;
|
|
1869
|
+
}): ResolvedCallSite;
|
|
1870
|
+
/** Resolves a `new` expression to the constructor it runs, if any. */
|
|
1871
|
+
resolveConstructor(args: {
|
|
1872
|
+
checker: default_2.TypeChecker;
|
|
1873
|
+
expression: default_2.NewExpression;
|
|
1874
|
+
}): ResolvedCallSite;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* The bookkeeping Tarjan's algorithm carries.
|
|
1879
|
+
*
|
|
1880
|
+
* Threaded as one object so the traversal can be split into small steps
|
|
1881
|
+
* without any of them taking a long parameter list.
|
|
1882
|
+
*/
|
|
1883
|
+
export declare interface TarjanState {
|
|
1884
|
+
readonly componentIdByCallable: Map<CallableId, number>;
|
|
1885
|
+
readonly frames: TraversalFrame[];
|
|
1886
|
+
readonly lowLink: Map<CallableId, number>;
|
|
1887
|
+
readonly memberIdsByComponent: CallableId[][];
|
|
1888
|
+
readonly onStack: Set<CallableId>;
|
|
1889
|
+
readonly order: Map<CallableId, number>;
|
|
1890
|
+
readonly pending: CallableId[];
|
|
1891
|
+
sequence: number;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Directory holding a project's test scaffolding.
|
|
1896
|
+
*
|
|
1897
|
+
* Its files are not named like tests — `mocks.ts`, `setup.ts` — but they exist
|
|
1898
|
+
* only for tests, and a report that counted them would describe a package's
|
|
1899
|
+
* fixtures as its control flow.
|
|
1900
|
+
*/
|
|
1901
|
+
export declare const TEST_DIRECTORY_SEGMENT = "testing";
|
|
1902
|
+
|
|
1903
|
+
/** Matches a test file, whatever tier it declares. */
|
|
1904
|
+
export declare const TEST_FILE_PATTERN: RegExp;
|
|
1905
|
+
|
|
1906
|
+
/** An interface member too many classes implement to be worth following. */
|
|
1907
|
+
export declare const TOO_MANY_IMPLEMENTATIONS_CALL: ResolvedCallSite;
|
|
1908
|
+
|
|
1909
|
+
/** One frame of the explicit stack the iterative traversal walks. */
|
|
1910
|
+
export declare interface TraversalFrame {
|
|
1911
|
+
readonly callableId: CallableId;
|
|
1912
|
+
/** How many successors of this node have been visited so far. */
|
|
1913
|
+
successorIndex: number;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
/**
|
|
1917
|
+
* A resolution that did not name exactly one callable.
|
|
1918
|
+
*
|
|
1919
|
+
* Derived from `CallableAddressResolution` rather than restated, so anything
|
|
1920
|
+
* acting on a failure keeps covering every way one can happen.
|
|
1921
|
+
*/
|
|
1922
|
+
export declare type UnresolvedCallableAddress = Exclude<CallableAddressResolution, {
|
|
1923
|
+
readonly kind: "resolved";
|
|
1924
|
+
}>;
|
|
1925
|
+
|
|
1926
|
+
/** A declared entry-point address that did not name exactly one callable. */
|
|
1927
|
+
export declare interface UnresolvedEntryPointAddress {
|
|
1928
|
+
readonly address: string;
|
|
1929
|
+
/**
|
|
1930
|
+
* The project whose configuration declared it. Absent when the workspace
|
|
1931
|
+
* configuration did, which has no project to name.
|
|
1932
|
+
*/
|
|
1933
|
+
readonly projectName: string | undefined;
|
|
1934
|
+
/** Why it named no single callable, and what it could have meant instead. */
|
|
1935
|
+
readonly resolution: UnresolvedCallableAddress;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/** Arguments for walking the projects a set of starting roots' imports reach. */
|
|
1939
|
+
declare interface WalkImportedProjectClosureArguments {
|
|
1940
|
+
/** Reports the workspace-relative paths one project's program pulled in. */
|
|
1941
|
+
readonly resolveProjectFiles: ResolveProjectFilesFunction;
|
|
1942
|
+
/** Project roots the traversal begins from. Always present in the result. */
|
|
1943
|
+
readonly startingProjects: readonly WorkspaceProject[];
|
|
1944
|
+
/**
|
|
1945
|
+
* Every project known to the workspace, not only the ones reached so far —
|
|
1946
|
+
* this is what lets a pulled-in file resolve to a project the traversal has
|
|
1947
|
+
* not visited yet.
|
|
1948
|
+
*/
|
|
1949
|
+
readonly workspaceProjects: readonly WorkspaceProject[];
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* Provides project discovery, module identity, and file exclusion.
|
|
1954
|
+
*/
|
|
1955
|
+
export declare class WorkspaceModule {
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/** One project discovered from a directory holding its own `tsconfig.json`. */
|
|
1959
|
+
export declare interface WorkspaceProject {
|
|
1960
|
+
/** Absolute path to the project's `tsconfig.json`. */
|
|
1961
|
+
readonly configurationPath: string;
|
|
1962
|
+
/**
|
|
1963
|
+
* Whether the project's root holds a `package.json`, read once when the
|
|
1964
|
+
* project is discovered.
|
|
1965
|
+
*
|
|
1966
|
+
* A fact about the directory rather than a policy about it — what it is
|
|
1967
|
+
* used for is `WorkspaceService.isClosureDestination`, and
|
|
1968
|
+
* `PACKAGE_MANIFEST_NAME` holds why. Carried on the project so the closure
|
|
1969
|
+
* traversal is a walk over data rather than over the filesystem: it is
|
|
1970
|
+
* consulted once per pulled-in *path*, and a project reached late would
|
|
1971
|
+
* otherwise be stat-ed thousands of times before it was reached at all.
|
|
1972
|
+
*/
|
|
1973
|
+
readonly hasPackageManifest: boolean;
|
|
1974
|
+
/** Same as `root`: the project's own directory is its identity. */
|
|
1975
|
+
readonly name: string;
|
|
1976
|
+
/** Workspace-relative project root, POSIX separators. */
|
|
1977
|
+
readonly root: string;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/** Finds the projects a run traces, and says which one owns a file. */
|
|
1981
|
+
export declare class WorkspaceService {
|
|
1982
|
+
private readonly logger;
|
|
1983
|
+
constructor(logger: LoggerService);
|
|
1984
|
+
/** Walks the whole workspace for every directory holding a `tsconfig.json`. */
|
|
1985
|
+
private findAllProjectDirectories;
|
|
1986
|
+
/**
|
|
1987
|
+
* Walks a directory recursively, collecting every subdirectory that holds
|
|
1988
|
+
* its own `tsconfig.json`.
|
|
1989
|
+
*
|
|
1990
|
+
* Descends into a `tsconfig.json`-holding directory too rather than
|
|
1991
|
+
* stopping there: a project nesting a second program under it — a
|
|
1992
|
+
* `testing/tsconfig.json`, a generated subpackage — is not a reason to miss
|
|
1993
|
+
* everything below it, and the directories this skips already keep the walk
|
|
1994
|
+
* from wandering into a dependency or a build artifact.
|
|
1995
|
+
*/
|
|
1996
|
+
private findProjectDirectories;
|
|
1997
|
+
/**
|
|
1998
|
+
* True when a project may be pulled into another project's closure.
|
|
1999
|
+
*
|
|
2000
|
+
* Two roots are refused. One holding no `package.json` is a directory of
|
|
2001
|
+
* shared settings rather than a package — `PACKAGE_MANIFEST_NAME` holds that
|
|
2002
|
+
* reasoning, and what it costs. The workspace root is refused whatever it
|
|
2003
|
+
* holds: a project whose root contains every other project cannot be a
|
|
2004
|
+
* meaningful dependency of any of them, and admitting it is the same
|
|
2005
|
+
* explosion reached from a root-level file — a `codometer.config.ts`, a
|
|
2006
|
+
* `scripts/` directory — rather than from a shared settings one.
|
|
2007
|
+
*/
|
|
2008
|
+
private isClosureDestination;
|
|
2009
|
+
/**
|
|
2010
|
+
* True when a project's root is a path-segment prefix of a file's path.
|
|
2011
|
+
*
|
|
2012
|
+
* A plain `startsWith` on the raw strings would let `packages/callidescope`
|
|
2013
|
+
* falsely contain `packages/ic-suite/callidescope/callidescope-graph/...` — the empty root is the
|
|
2014
|
+
* one exception, since the workspace root itself contains every file.
|
|
2015
|
+
*/
|
|
2016
|
+
private isContainedByRoot;
|
|
2017
|
+
/**
|
|
2018
|
+
* True for a directory a whole-workspace scan should never descend into.
|
|
2019
|
+
*
|
|
2020
|
+
* A name holding `{{`/`}}` is a scaffolding template's own placeholder
|
|
2021
|
+
* directory, never a real one — its `tsconfig.json`, if it has one, is
|
|
2022
|
+
* written for a generator to fill in later and cannot build a program on
|
|
2023
|
+
* its own.
|
|
2024
|
+
*/
|
|
2025
|
+
private isExcludedFromScan;
|
|
2026
|
+
/** True when an exclusion already names the project's own `tsconfig.json`. */
|
|
2027
|
+
private isExcludedProject;
|
|
2028
|
+
/**
|
|
2029
|
+
* Resolves the project directories a run will trace.
|
|
2030
|
+
*
|
|
2031
|
+
* Each of `args.directories` is trusted as a project root outright rather
|
|
2032
|
+
* than searched for a `tsconfig.json` beneath it: naming a directory is
|
|
2033
|
+
* the caller saying exactly what it means to trace. Passing none is what
|
|
2034
|
+
* asks for the whole workspace instead, found by walking it for every
|
|
2035
|
+
* `tsconfig.json` there is — the only case that needs a search at all.
|
|
2036
|
+
*
|
|
2037
|
+
* A project an exclusion names is dropped here, before its `tsconfig.json`
|
|
2038
|
+
* is ever opened. Excluding later — once the files a program yielded are
|
|
2039
|
+
* being filtered — is too late to help: reading the configuration is itself
|
|
2040
|
+
* what fails on a `tsconfig.json` written to be unreadable, so an exclusion
|
|
2041
|
+
* that only reaches the files cannot keep the run away from it.
|
|
2042
|
+
*
|
|
2043
|
+
* A named directory holding no `tsconfig.json` ends the run through
|
|
2044
|
+
* `ProgramConfigurationError`, the same way one holding an unreadable
|
|
2045
|
+
* `tsconfig.json` does. Naming a directory is the caller saying it should be
|
|
2046
|
+
* traced, so a run that quietly traced one fewer project than it was asked
|
|
2047
|
+
* to would report depths for a workspace nobody described — and a typo in a
|
|
2048
|
+
* `--directories` list would pass every gate for having looked at less. The
|
|
2049
|
+
* whole-workspace walk cannot reach this: it only ever yields directories a
|
|
2050
|
+
* `tsconfig.json` was found in.
|
|
2051
|
+
*/
|
|
2052
|
+
discoverProjects(args: DiscoverProjectsArguments): WorkspaceProject[];
|
|
2053
|
+
/** True when a path names a test file, or scaffolding written for tests. */
|
|
2054
|
+
isTestFile(filePath: string): boolean;
|
|
2055
|
+
/**
|
|
2056
|
+
* Names the traced project whose root most narrowly contains a file.
|
|
2057
|
+
*
|
|
2058
|
+
* "Contains" means the deepest of `args.projects` whose root is a
|
|
2059
|
+
* path-segment prefix of `args.workspaceRelativePath` — never a shallower
|
|
2060
|
+
* ancestor, and never a sibling that merely shares a string prefix. This is
|
|
2061
|
+
* what settles a project that nests a second `tsconfig.json` beneath it (a
|
|
2062
|
+
* `testing/tsconfig.json`, a generated subpackage): the nested root is more
|
|
2063
|
+
* specific than the parent's, so a file under it belongs to the nested
|
|
2064
|
+
* project even when the parent's own configuration also lists that file.
|
|
2065
|
+
*
|
|
2066
|
+
* Returns `undefined` when none of `args.projects` contains the file at
|
|
2067
|
+
* all — impossible for a whole-workspace run, since the workspace root
|
|
2068
|
+
* itself is always a traced project, but reachable when a run is scoped to
|
|
2069
|
+
* a handful of directories that do not include it.
|
|
2070
|
+
*/
|
|
2071
|
+
resolveOwningProject(args: {
|
|
2072
|
+
projects: readonly WorkspaceProject[];
|
|
2073
|
+
workspaceRelativePath: string;
|
|
2074
|
+
}): undefined | WorkspaceProject;
|
|
2075
|
+
/** Rewrites an absolute path as workspace-relative with POSIX separators. */
|
|
2076
|
+
toWorkspaceRelative(args: {
|
|
2077
|
+
absolutePath: string;
|
|
2078
|
+
workspaceRoot: string;
|
|
2079
|
+
}): string;
|
|
2080
|
+
/**
|
|
2081
|
+
* Walks the projects a set of starting roots' imports transitively reach —
|
|
2082
|
+
* a starting project's dependency closure — asking
|
|
2083
|
+
* `args.resolveProjectFiles` about each one exactly once.
|
|
2084
|
+
*
|
|
2085
|
+
* Named for the imports it follows rather than for dependency in general,
|
|
2086
|
+
* because `ProjectsService.resolveDependencyClosure` in `@callidescope/nx`
|
|
2087
|
+
* resolves a closure too, from the edges the Nx graph declares. Both are
|
|
2088
|
+
* real closures over different edges, and the two sets are not the same.
|
|
2089
|
+
*
|
|
2090
|
+
* `args.resolveProjectFiles` reports the workspace-relative paths one
|
|
2091
|
+
* project's program pulled in; this method owns only the fixed-point walk
|
|
2092
|
+
* over those reports, never how a program comes to exist. Each reported
|
|
2093
|
+
* path is walked back to its owning project through `resolveOwningProject`
|
|
2094
|
+
* against `args.workspaceProjects`, so a path `node_modules` holds, or one
|
|
2095
|
+
* no traced project's root contains, resolves to `undefined` and never
|
|
2096
|
+
* manufactures a project that is not there. A project already reached is
|
|
2097
|
+
* never asked again, which is what makes a cycle between two projects
|
|
2098
|
+
* terminate instead of looping forever. A resolved owner that is not a
|
|
2099
|
+
* closure *destination* is dropped as though nothing owned the path —
|
|
2100
|
+
* `isClosureDestination` says which projects those are, and why.
|
|
2101
|
+
*
|
|
2102
|
+
* Every starting project is asked about, even one whose program pulls in
|
|
2103
|
+
* nothing outside itself and even one no closure could have reached, and a
|
|
2104
|
+
* project's dependents never are — nothing here walks from a file to
|
|
2105
|
+
* whoever imports it, only from a project to what its own program reaches.
|
|
2106
|
+
*
|
|
2107
|
+
* The callback's invocations are the whole result, which is why nothing is
|
|
2108
|
+
* returned: the closure is exactly the set of projects the callback was
|
|
2109
|
+
* asked about, and handing that set back as well would be a second
|
|
2110
|
+
* representation of it, free to drift from whatever the caller collected
|
|
2111
|
+
* while it answered. Ordering belongs to the caller for the same reason —
|
|
2112
|
+
* `ProgramService.buildPrograms` sorts the programs it collected, so the
|
|
2113
|
+
* order a report reads in is decided once, over the things a report is
|
|
2114
|
+
* really made of.
|
|
2115
|
+
*/
|
|
2116
|
+
walkImportedProjectClosure(args: WalkImportedProjectClosureArguments): void;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
export { }
|