@suss/adapter-typescript 0.0.2 → 0.1.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/README.md +1 -1
- package/dist/index.cjs +5140 -1975
- package/dist/index.d.cts +486 -87
- package/dist/index.d.ts +486 -87
- package/dist/index.js +5101 -1932
- package/package.json +10 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,54 +1,27 @@
|
|
|
1
|
-
import { FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration,
|
|
2
|
-
import { RawCondition, DiscoveryPattern, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure,
|
|
3
|
-
import { BehavioralSummary, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
|
|
1
|
+
import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, Project, Expression } from 'ts-morph';
|
|
2
|
+
import { TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, EffectArg, RawDeclaredContract, ContractPattern, ResponsePropertyMapping } from '@suss/extractor';
|
|
3
|
+
import { BehavioralSummary, MessageBusSemantics, DeployableUnit, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Reported by `lookup`. Surfaces what the cache decided and, for
|
|
7
|
-
* partial-hits, the file-churn breakdown so callers can render a
|
|
8
|
-
* diagnostic.
|
|
9
|
-
*/
|
|
5
|
+
/** Reported by `lookup`: what the cache decided, and why. */
|
|
10
6
|
interface CacheDiagnostic {
|
|
11
|
-
kind: "hit" | "
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
reusedSummaries: number;
|
|
21
|
-
/** Files the adapter needs to re-extract per-file from. */
|
|
22
|
-
filesToReExtract: number;
|
|
23
|
-
/** Files added to the include set since the cached run. */
|
|
24
|
-
addedFiles: number;
|
|
25
|
-
/** Files removed from the include set since the cached run. */
|
|
26
|
-
removedFiles: number;
|
|
27
|
-
/** Files whose mtime/size differs (excludes added/removed). */
|
|
28
|
-
changedFiles: number;
|
|
29
|
-
};
|
|
7
|
+
kind: "hit" | "miss";
|
|
8
|
+
/**
|
|
9
|
+
* Reason the lookup missed (only set when kind === "miss").
|
|
10
|
+
* `key-changed` means the cache directory holds entries, but none
|
|
11
|
+
* under this run's schema, adapter, packs and tsconfig path.
|
|
12
|
+
* `files-changed` means the include set is not the one the entry
|
|
13
|
+
* was written from.
|
|
14
|
+
*/
|
|
15
|
+
missReason?: "no-manifest" | "key-changed" | "tsconfig-changed" | "files-changed";
|
|
30
16
|
}
|
|
31
17
|
/**
|
|
32
|
-
* Result of a cache lookup.
|
|
33
|
-
*
|
|
34
|
-
* - "hit" — the entire summary set is valid; return it
|
|
35
|
-
* verbatim, skip extraction.
|
|
36
|
-
* - "partial-hit" — `kept` is correct as far as their location.file
|
|
37
|
-
* went; re-extract `filesToExtract` per-file and
|
|
38
|
-
* re-run the closure / rethrow passes over the
|
|
39
|
-
* merged set.
|
|
40
|
-
* - "miss" — manifest absent or coarse keys (schema, packs,
|
|
41
|
-
* tsconfig) changed; full re-extract.
|
|
18
|
+
* Result of a cache lookup. A hit carries the whole summary set; a miss
|
|
19
|
+
* says why, so a caller can render the reason.
|
|
42
20
|
*/
|
|
43
21
|
type CacheLookup = {
|
|
44
22
|
kind: "hit";
|
|
45
23
|
summaries: BehavioralSummary[];
|
|
46
24
|
diagnostic: CacheDiagnostic;
|
|
47
|
-
} | {
|
|
48
|
-
kind: "partial-hit";
|
|
49
|
-
kept: BehavioralSummary[];
|
|
50
|
-
filesToExtract: string[];
|
|
51
|
-
diagnostic: CacheDiagnostic;
|
|
52
25
|
} | {
|
|
53
26
|
kind: "miss";
|
|
54
27
|
diagnostic: CacheDiagnostic;
|
|
@@ -57,6 +30,25 @@ type CacheLookup = {
|
|
|
57
30
|
/** One pack's path through the funnel. */
|
|
58
31
|
interface PackFunnel {
|
|
59
32
|
pack: string;
|
|
33
|
+
/**
|
|
34
|
+
* What the pack calls this build of itself, or null when it declares
|
|
35
|
+
* nothing. The cache keys on it, so a pack that never changes its
|
|
36
|
+
* stamp can answer a later run with an earlier build's results.
|
|
37
|
+
*/
|
|
38
|
+
version: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the pack looks for units of its own at all. A pack made
|
|
41
|
+
* only of recognisers contributes effects to units other packs found,
|
|
42
|
+
* so discovering nothing is how it always behaves and says nothing
|
|
43
|
+
* about whether it is working.
|
|
44
|
+
*/
|
|
45
|
+
discovers: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the pack carries recognisers. Those fire inside units other
|
|
48
|
+
* packs discovered, so a pack made only of them contributes effects
|
|
49
|
+
* and never a summary.
|
|
50
|
+
*/
|
|
51
|
+
recognizes: boolean;
|
|
60
52
|
/**
|
|
61
53
|
* Import specifiers that make a file relevant to this pack. Empty
|
|
62
54
|
* for an ungated pack, which walks every file.
|
|
@@ -74,8 +66,51 @@ interface PackFunnel {
|
|
|
74
66
|
candidateFiles: number;
|
|
75
67
|
/** Units discovered across those files. */
|
|
76
68
|
unitsDiscovered: number;
|
|
69
|
+
/**
|
|
70
|
+
* Unit bodies any pack walked in the files this pack's gate selected.
|
|
71
|
+
*
|
|
72
|
+
* This is what a recogniser pack had the chance to fire on. Its own
|
|
73
|
+
* discovery count says nothing, since it discovers nothing by design,
|
|
74
|
+
* and its candidate-file count says nothing either, because a
|
|
75
|
+
* recogniser only runs where some pack found a unit to walk.
|
|
76
|
+
*/
|
|
77
|
+
unitsInGatedFiles: number;
|
|
78
|
+
/** Effects this pack's recognisers returned. */
|
|
79
|
+
effectsRecognized: number;
|
|
80
|
+
/**
|
|
81
|
+
* Units this pack kept. A unit an earlier pack already claimed is
|
|
82
|
+
* dropped here, so a pack can discover plenty and keep none when it
|
|
83
|
+
* sits behind a pack that recognises the same code.
|
|
84
|
+
*/
|
|
85
|
+
unitsClaimed: number;
|
|
86
|
+
/**
|
|
87
|
+
* Units this pack discovered twice over. Cross-pack dedup is the
|
|
88
|
+
* point of the claim set, but a pack colliding with itself means its
|
|
89
|
+
* own discovery patterns overlap and one of the two is being thrown
|
|
90
|
+
* away without anybody deciding which.
|
|
91
|
+
*/
|
|
92
|
+
selfCollisions: number;
|
|
77
93
|
/** Summaries built from those units. */
|
|
78
94
|
summariesProduced: number;
|
|
95
|
+
/** Summaries in the finished run that name this pack as what recognised them. */
|
|
96
|
+
summariesBound: number;
|
|
97
|
+
/**
|
|
98
|
+
* Bound summaries on the provider side of their boundary.
|
|
99
|
+
*
|
|
100
|
+
* Transitions say what a unit does with a request, which is a
|
|
101
|
+
* question only a provider answers. A consumer records what it reads
|
|
102
|
+
* back from the response instead, and that lives on the summary's
|
|
103
|
+
* metadata. Measuring a client pack against transitions would report
|
|
104
|
+
* every working one as extracting nothing.
|
|
105
|
+
*/
|
|
106
|
+
providerSummaries: number;
|
|
107
|
+
/** Provider summaries carrying at least one transition. */
|
|
108
|
+
summariesWithBehavior: number;
|
|
109
|
+
/**
|
|
110
|
+
* Where one of this pack's hooks threw. Every count above is a floor
|
|
111
|
+
* while this is non-empty.
|
|
112
|
+
*/
|
|
113
|
+
failures: PackFailure[];
|
|
79
114
|
}
|
|
80
115
|
interface ExtractionReport {
|
|
81
116
|
/** Files in the tsconfig include set, or null when the caller supplied a Project. */
|
|
@@ -91,6 +126,165 @@ interface ExtractionReport {
|
|
|
91
126
|
emptyStage: EmptyStage | null;
|
|
92
127
|
}
|
|
93
128
|
type EmptyStage = "tsconfig" | "gateResolution" | "candidateFiles" | "discovery" | "assembly";
|
|
129
|
+
/**
|
|
130
|
+
* A pack's hook throwing on one file.
|
|
131
|
+
*
|
|
132
|
+
* The run carries on with the other files, so every count for that pack
|
|
133
|
+
* afterwards is a floor rather than a total. Somebody reading those
|
|
134
|
+
* counts has to be told, or a pack that broke reads the same as a pack
|
|
135
|
+
* that looked and found nothing.
|
|
136
|
+
*/
|
|
137
|
+
interface PackFailure {
|
|
138
|
+
/** The hook that threw, named as a pack author would know it. */
|
|
139
|
+
hook: string;
|
|
140
|
+
/** The file the pack was reading. */
|
|
141
|
+
file: string;
|
|
142
|
+
message: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** One question: do these files reach any of these packages. */
|
|
146
|
+
interface FileSetQuery {
|
|
147
|
+
sourceFiles: ReadonlyArray<SourceFile>;
|
|
148
|
+
packages: ReadonlyArray<string>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
declare class ResolutionStore {
|
|
152
|
+
private readonly db;
|
|
153
|
+
private readonly table;
|
|
154
|
+
private readonly fullyExtracted;
|
|
155
|
+
private readonly seededValues;
|
|
156
|
+
private readonly importedNames;
|
|
157
|
+
private readonly graph;
|
|
158
|
+
private stale;
|
|
159
|
+
constructor(wrappers?: TransparentWrapper[]);
|
|
160
|
+
/**
|
|
161
|
+
* The function `value` resolves to, or null when no chain reaches
|
|
162
|
+
* one. The result is a ts-morph node in whatever file the function
|
|
163
|
+
* actually lives.
|
|
164
|
+
*
|
|
165
|
+
* Facts come in waves: extract the file the value lives in, ask, and
|
|
166
|
+
* only widen to the files it imports when the answer is still
|
|
167
|
+
* missing. A value that resolves without leaving its own file costs
|
|
168
|
+
* one file of extraction, not the whole import closure.
|
|
169
|
+
*/
|
|
170
|
+
resolveCallable(value: Node): Node | null;
|
|
171
|
+
/**
|
|
172
|
+
* The object literal `value` ends up being, or null. The route a
|
|
173
|
+
* registration call names often lives on a shared object built in
|
|
174
|
+
* another file, and this is how discovery reads it back.
|
|
175
|
+
*/
|
|
176
|
+
resolveObject(value: Node): Node | null;
|
|
177
|
+
/**
|
|
178
|
+
* The expression `value` is written as, or null. Unlike the other two
|
|
179
|
+
* questions this one does not care what kind of expression it lands
|
|
180
|
+
* on, so it answers for values that are neither functions nor
|
|
181
|
+
* objects: a GraphQL document held in a named constant in another
|
|
182
|
+
* file comes back as the template literal or tag call that built it.
|
|
183
|
+
*
|
|
184
|
+
* A value the code computes has no written form to report, so the
|
|
185
|
+
* answer is the computing expression itself and the caller gets
|
|
186
|
+
* nothing it can use, which is the point.
|
|
187
|
+
*/
|
|
188
|
+
resolveWrittenValue(value: Node): Node | null;
|
|
189
|
+
/**
|
|
190
|
+
* The names from `modules` this value stands for. A value stands for
|
|
191
|
+
* a library name two ways, and a caller asking which library concept
|
|
192
|
+
* something is wants both:
|
|
193
|
+
*
|
|
194
|
+
* - it is that name, under whatever local spelling. `import
|
|
195
|
+
* { Controller as Resource }` makes `Resource` the library's
|
|
196
|
+
* `Controller`.
|
|
197
|
+
* - calling it calls that name. A project decorator written as
|
|
198
|
+
* `(path) => Controller(path)` marks a controller, and so does
|
|
199
|
+
* one written as `applyDecorators(Controller(path))`.
|
|
200
|
+
*
|
|
201
|
+
* Several names is the normal answer rather than an ambiguity, since
|
|
202
|
+
* a wrapper composing two library decorators applies both. The caller
|
|
203
|
+
* asks whether the name it cares about is among them.
|
|
204
|
+
*
|
|
205
|
+
* One wrapper is applied across hundreds of files, so the answer is
|
|
206
|
+
* memoized against the declaration it was asked about. Without that,
|
|
207
|
+
* every use site seeds a fresh value and pays another fixpoint.
|
|
208
|
+
*/
|
|
209
|
+
importedNamesOf(value: Node, modules: string[]): string[];
|
|
210
|
+
/**
|
|
211
|
+
* The names from `modules` this value stands for, or null while the
|
|
212
|
+
* walk should keep widening.
|
|
213
|
+
*
|
|
214
|
+
* The distinction is what keeps the cost down. A value that has
|
|
215
|
+
* arrived at a package has nothing more to give, because a library's
|
|
216
|
+
* own body is not here to read, so a decorator from a library the
|
|
217
|
+
* caller did not ask about stops on the first wave rather than
|
|
218
|
+
* running out the import closure looking for a match it will never
|
|
219
|
+
* find. A value that has only arrived at another file in the project
|
|
220
|
+
* keeps going, since the wrapper it names is declared somewhere the
|
|
221
|
+
* walk has not reached yet.
|
|
222
|
+
*/
|
|
223
|
+
private lookupImportedNames;
|
|
224
|
+
private resolveByWaves;
|
|
225
|
+
private walkForAnswer;
|
|
226
|
+
/**
|
|
227
|
+
* The answer has been read, so the question and the chain that
|
|
228
|
+
* answered it can go. What the query extracted stays: files are the
|
|
229
|
+
* expensive part and the next query reads the same facts.
|
|
230
|
+
*
|
|
231
|
+
* Nothing is owed after this. The rules have seen every fact the
|
|
232
|
+
* database holds and concluded what the demand still present asks
|
|
233
|
+
* for, which is none, so the next query starts from its own asking
|
|
234
|
+
* fact.
|
|
235
|
+
*/
|
|
236
|
+
private forgetQuery;
|
|
237
|
+
/**
|
|
238
|
+
* Somebody asked about this value, which is what the rules follow
|
|
239
|
+
* chains for. Separate from `seedValue`, which only speaks for values
|
|
240
|
+
* that are expressions; a caller can ask about a declaration too, and
|
|
241
|
+
* the answer is still owed.
|
|
242
|
+
*/
|
|
243
|
+
private wantValue;
|
|
244
|
+
/**
|
|
245
|
+
* Facts for the queried value itself. File extraction only reaches
|
|
246
|
+
* values that hang off exports, and a query can be rooted anywhere:
|
|
247
|
+
* a registration call passes `routes.provision` as an argument, and
|
|
248
|
+
* nothing else ever emits facts for that expression.
|
|
249
|
+
*/
|
|
250
|
+
private seedValue;
|
|
251
|
+
private lookupObject;
|
|
252
|
+
/**
|
|
253
|
+
* Reaching two different expressions means the rules cannot tell
|
|
254
|
+
* which one the value is written as, and the same reasoning applies
|
|
255
|
+
* as for the other two questions: ambiguity is nothing. The value
|
|
256
|
+
* itself is not an answer, or every identifier would answer with
|
|
257
|
+
* itself the moment its chain went nowhere.
|
|
258
|
+
*/
|
|
259
|
+
private lookupWritten;
|
|
260
|
+
/**
|
|
261
|
+
* For each set of files and packages, which of those files reach any
|
|
262
|
+
* of those packages through their imports, following project-local
|
|
263
|
+
* re-export chains.
|
|
264
|
+
*/
|
|
265
|
+
filesImportingTransitively(fileSets: ReadonlyArray<FileSetQuery>): ReadonlyArray<ReadonlySet<SourceFile>>;
|
|
266
|
+
/**
|
|
267
|
+
* The one function this value resolves to. Several rules can reach
|
|
268
|
+
* the same answer, which is fine, but reaching two different
|
|
269
|
+
* functions means the rules cannot tell which one the value is, and
|
|
270
|
+
* picking whichever landed in the relation first would make the
|
|
271
|
+
* answer depend on the order facts arrived in. Ambiguity is nothing.
|
|
272
|
+
*/
|
|
273
|
+
private lookup;
|
|
274
|
+
/** Run the rules to fixpoint. */
|
|
275
|
+
private derive;
|
|
276
|
+
/**
|
|
277
|
+
* The second column of an answer relation, for the value asked about.
|
|
278
|
+
* The database indexes a column the first time somebody looks it up
|
|
279
|
+
* and keeps that index as facts arrive, so this is a map hit and the
|
|
280
|
+
* answers a query never asks about cost nothing.
|
|
281
|
+
*/
|
|
282
|
+
private answersFor;
|
|
283
|
+
/** The trailing pair of a three-column answer relation, joined. */
|
|
284
|
+
private answerPairsFor;
|
|
285
|
+
/** Emit a file's facts, unless some earlier query already did. */
|
|
286
|
+
private extractFile;
|
|
287
|
+
}
|
|
94
288
|
|
|
95
289
|
type FunctionRoot = FunctionDeclaration | FunctionExpression | ArrowFunction | MethodDeclaration;
|
|
96
290
|
/**
|
|
@@ -98,11 +292,6 @@ type FunctionRoot = FunctionDeclaration | FunctionExpression | ArrowFunction | M
|
|
|
98
292
|
* Use collectAncestorConditionInfos when you need the Expression nodes.
|
|
99
293
|
*/
|
|
100
294
|
declare function collectAncestorBranches(terminalNode: Node, functionRoot: FunctionRoot): RawCondition[];
|
|
101
|
-
/**
|
|
102
|
-
* Public API — returns RawCondition[] with structured: null.
|
|
103
|
-
* Use collectEarlyReturnConditionInfos when you need the Expression nodes.
|
|
104
|
-
*/
|
|
105
|
-
declare function collectEarlyReturns(terminalNode: Node, functionRoot: FunctionRoot): RawCondition[];
|
|
106
295
|
|
|
107
296
|
interface ClientCallSite {
|
|
108
297
|
callExpression: CallExpression;
|
|
@@ -110,12 +299,38 @@ interface ClientCallSite {
|
|
|
110
299
|
methodName: string | null;
|
|
111
300
|
}
|
|
112
301
|
interface DiscoveredUnit {
|
|
113
|
-
|
|
302
|
+
/**
|
|
303
|
+
* The function this unit is. Null when a registration announced a
|
|
304
|
+
* boundary and the handler it names comes from outside everything
|
|
305
|
+
* this run reads, in which case `announcedAt` is where the unit is
|
|
306
|
+
* and the summary describes a boundary with nothing behind it.
|
|
307
|
+
*/
|
|
308
|
+
func: FunctionRoot | null;
|
|
309
|
+
/**
|
|
310
|
+
* The registration that announced a boundary whose handler was never
|
|
311
|
+
* reached. Set exactly when `func` is null, and the unit's location.
|
|
312
|
+
*/
|
|
313
|
+
announcedAt?: Node;
|
|
114
314
|
kind: string;
|
|
115
315
|
name: string;
|
|
116
316
|
callSite?: ClientCallSite;
|
|
117
317
|
/** The discovery pattern that produced this unit. Set by discoverUnits. */
|
|
118
318
|
pattern?: DiscoveryPattern;
|
|
319
|
+
/**
|
|
320
|
+
* Terminal patterns for this unit's body, when a discoverUnits
|
|
321
|
+
* callback attached them (`DiscoveredCustomUnit.terminals`). A pack
|
|
322
|
+
* whose units follow more than one output convention declares the
|
|
323
|
+
* common one at pack level and attaches the other per unit.
|
|
324
|
+
* `extractCodeStructure` falls back to the pack-level `terminals`
|
|
325
|
+
* when unset.
|
|
326
|
+
*/
|
|
327
|
+
terminals?: TerminalPattern[];
|
|
328
|
+
/**
|
|
329
|
+
* Input mapping for this unit, when a discoverUnits callback attached
|
|
330
|
+
* one (`DiscoveredCustomUnit.inputMapping`). Falls back to the
|
|
331
|
+
* pack-level `inputMapping` when unset.
|
|
332
|
+
*/
|
|
333
|
+
inputMapping?: InputMappingPattern;
|
|
119
334
|
/**
|
|
120
335
|
* Populated by `resolverMap`-style discovery (GraphQL code-first).
|
|
121
336
|
* The adapter uses it to build a `graphql-resolver` binding directly,
|
|
@@ -201,6 +416,19 @@ interface DiscoveredUnit {
|
|
|
201
416
|
method: string;
|
|
202
417
|
path: string;
|
|
203
418
|
};
|
|
419
|
+
/**
|
|
420
|
+
* Populated by a pack's `discoverUnits` callback for a message-bus
|
|
421
|
+
* consumer whose channel the code names (a handler factory whose
|
|
422
|
+
* config carries the expected subject). The adapter uses it to build
|
|
423
|
+
* a `message-bus` binding directly, which pairs with producers
|
|
424
|
+
* sending on the same channel.
|
|
425
|
+
*/
|
|
426
|
+
channelInfo?: {
|
|
427
|
+
messageBus: MessageBusSemantics["messageBus"];
|
|
428
|
+
channel: string;
|
|
429
|
+
};
|
|
430
|
+
/** The thing that gets deployed and runs this unit, when known. */
|
|
431
|
+
deployableUnit?: DeployableUnit;
|
|
204
432
|
/**
|
|
205
433
|
* Metadata merged onto the assembled summary's `metadata` field.
|
|
206
434
|
* Populated when a pack's `discoverUnits` callback stamps provenance
|
|
@@ -214,7 +442,7 @@ interface DiscoveredUnit {
|
|
|
214
442
|
* Discover code units in `sourceFile` by running all patterns.
|
|
215
443
|
* Deduplicates entries with the same function node and kind.
|
|
216
444
|
*/
|
|
217
|
-
declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[]): DiscoveredUnit[];
|
|
445
|
+
declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore): DiscoveredUnit[];
|
|
218
446
|
|
|
219
447
|
interface TimingReport {
|
|
220
448
|
totalMs: number;
|
|
@@ -240,7 +468,7 @@ interface Timer {
|
|
|
240
468
|
*/
|
|
241
469
|
type DescentBarriers = ReadonlySet<Node>;
|
|
242
470
|
|
|
243
|
-
declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack,
|
|
471
|
+
declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack, invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers): RawCodeStructure;
|
|
244
472
|
interface TypeScriptAdapterConfig {
|
|
245
473
|
tsConfigFilePath?: string;
|
|
246
474
|
project?: Project;
|
|
@@ -284,13 +512,15 @@ interface TypeScriptAdapterConfig {
|
|
|
284
512
|
*/
|
|
285
513
|
onExtractionReport?: (report: ExtractionReport) => void;
|
|
286
514
|
/**
|
|
287
|
-
* On-disk cache directory for the
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* the
|
|
515
|
+
* On-disk cache directory for the extraction cache. Pass an absolute
|
|
516
|
+
* path; defaults to `.suss/cache/` next to the tsconfig, and to no
|
|
517
|
+
* cache at all when no tsconfig path is supplied. Pass `null` to turn
|
|
518
|
+
* caching off. A warm run with nothing changed costs one stat per
|
|
519
|
+
* file and returns the previous run's summaries whole.
|
|
520
|
+
*
|
|
521
|
+
* A process that loaded the adapter from source gets no cache
|
|
522
|
+
* whatever this says, because a key made there cannot tell one build
|
|
523
|
+
* of the adapter from another.
|
|
294
524
|
*/
|
|
295
525
|
cacheDir?: string | null;
|
|
296
526
|
}
|
|
@@ -308,20 +538,68 @@ interface TypeScriptAdapter {
|
|
|
308
538
|
}
|
|
309
539
|
declare function createTypeScriptAdapter(config: TypeScriptAdapterConfig): TypeScriptAdapter;
|
|
310
540
|
|
|
541
|
+
interface FoundTerminal {
|
|
542
|
+
node: Node;
|
|
543
|
+
terminal: RawTerminal;
|
|
544
|
+
/**
|
|
545
|
+
* The return this terminal came from: a ReturnStatement, or the body
|
|
546
|
+
* of an arrow that returns without writing `return`. Absent when the
|
|
547
|
+
* terminal is not a return at all, as a throw is not.
|
|
548
|
+
*
|
|
549
|
+
* The matcher is the only thing that knows, because `node` is
|
|
550
|
+
* wherever the matcher stopped looking, which differs by matcher.
|
|
551
|
+
* Anything downstream that guessed instead got it wrong.
|
|
552
|
+
*/
|
|
553
|
+
source?: Node;
|
|
554
|
+
/**
|
|
555
|
+
* A test that also has to hold for this terminal to be the one that
|
|
556
|
+
* fires, on top of whatever the path to it required.
|
|
557
|
+
*
|
|
558
|
+
* One call site answers with more than one status when the status is
|
|
559
|
+
* written as a choice, and the arms are told apart by nothing else:
|
|
560
|
+
* they share a node, so the path engine gives them the same
|
|
561
|
+
* conditions.
|
|
562
|
+
*/
|
|
563
|
+
whenAlso?: RawCondition;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Walk every descendant of `func` and try each pattern in order (first match
|
|
568
|
+
* wins per node). Returns all matched terminals across the whole function.
|
|
569
|
+
*
|
|
570
|
+
* Descends into nested function expressions / arrows so a unit whose
|
|
571
|
+
* terminal output is produced inside a Promise executor or `.then`
|
|
572
|
+
* callback (`res.json(data)` on the unit's own `res` parameter) is still
|
|
573
|
+
* discovered. Inside a nested function only escaping terminals are
|
|
574
|
+
* matched — see `NESTED_ESCAPING_MATCH_TYPES`.
|
|
575
|
+
*/
|
|
576
|
+
declare function findTerminals(func: FunctionRoot, patterns: TerminalPattern[], barriers?: DescentBarriers): FoundTerminal[];
|
|
577
|
+
|
|
311
578
|
/**
|
|
312
579
|
* Extract all raw branches from a function, composing:
|
|
313
|
-
* 1. findTerminals
|
|
314
|
-
* 2.
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
580
|
+
* 1. findTerminals, to locate terminal nodes
|
|
581
|
+
* 2. computePathConditions, for the conditions on each path (CFG
|
|
582
|
+
* enumeration; declined shapes degrade to sound
|
|
583
|
+
* under-specification)
|
|
584
|
+
* 3. parseConditionExpression, turning an Expression into a Predicate
|
|
585
|
+
* 4. extractInvocationEffects, for bare expression-statement calls,
|
|
586
|
+
* attached to the default branch so handler and useEffect bodies
|
|
587
|
+
* carry their side-effect set
|
|
320
588
|
*
|
|
321
589
|
* `isDefault` is true when the branch has no conditions, or all
|
|
322
|
-
* conditions come from early returns
|
|
590
|
+
* conditions come from early returns or throws.
|
|
323
591
|
*/
|
|
324
|
-
|
|
592
|
+
/**
|
|
593
|
+
* The branches a function produces, alongside the terminals they came
|
|
594
|
+
* from. A caller that wants to know which returns went unclaimed needs
|
|
595
|
+
* the terminals, and searching for them a second time costs about as
|
|
596
|
+
* much as this whole pass.
|
|
597
|
+
*/
|
|
598
|
+
interface RawBranchResult {
|
|
599
|
+
branches: RawBranch[];
|
|
600
|
+
terminals: FoundTerminal[];
|
|
601
|
+
}
|
|
602
|
+
declare function extractRawBranches(func: FunctionRoot, terminalPatterns: TerminalPattern[], invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers): RawBranchResult;
|
|
325
603
|
|
|
326
604
|
/**
|
|
327
605
|
* A Project covering `rootDir`, for a codebase with no tsconfig to read.
|
|
@@ -334,6 +612,53 @@ declare function createProjectWithoutTsconfig(rootDir: string): {
|
|
|
334
612
|
/** The nearest tsconfig or jsconfig at or above `startDir`, or null. */
|
|
335
613
|
declare function findNearestTsconfig(startDir: string): string | null;
|
|
336
614
|
|
|
615
|
+
/**
|
|
616
|
+
* A call a project names as its own dispatcher.
|
|
617
|
+
*
|
|
618
|
+
* `receiver` is the type name, not the variable: a service holds its
|
|
619
|
+
* dispatcher in a field, a closure, or a constructor parameter, and
|
|
620
|
+
* only the type is stable across those.
|
|
621
|
+
*/
|
|
622
|
+
interface ConfiguredCallSpec {
|
|
623
|
+
/** Module the receiver's type is declared in. */
|
|
624
|
+
module: string;
|
|
625
|
+
/** Type name of the receiver, as exported from that module. */
|
|
626
|
+
receiver: string;
|
|
627
|
+
/** Method that performs the send. */
|
|
628
|
+
method: string;
|
|
629
|
+
/** Argument index carrying the subject. */
|
|
630
|
+
subjectArg: number;
|
|
631
|
+
/**
|
|
632
|
+
* Argument index carrying the message body. Left out when the
|
|
633
|
+
* method takes no single body argument (a batch method takes a
|
|
634
|
+
* list of entries), and then the read carries no body.
|
|
635
|
+
*/
|
|
636
|
+
bodyArg?: number;
|
|
637
|
+
}
|
|
638
|
+
interface ConfiguredCallRead {
|
|
639
|
+
/** The subject the call names, always a string the source states. */
|
|
640
|
+
subject: string;
|
|
641
|
+
/** The body argument's extracted shape, or null when none applies. */
|
|
642
|
+
body: EffectArg | null;
|
|
643
|
+
/** Source text of the callee, for the effect's `callee` field. */
|
|
644
|
+
callee: string;
|
|
645
|
+
}
|
|
646
|
+
/** The parts of a recognizer context this helper needs. */
|
|
647
|
+
interface ConfiguredCallContext {
|
|
648
|
+
sourceFile: SourceFile;
|
|
649
|
+
extractArgs(): EffectArg[];
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Read `call` as the configured send, or answer null.
|
|
653
|
+
*
|
|
654
|
+
* A subject that is not a string the source states (a variable built
|
|
655
|
+
* at runtime, a template with substitutions, a computed key) answers
|
|
656
|
+
* null, and the pack emits nothing. Naming a channel we cannot read
|
|
657
|
+
* would pair a producer against the wrong consumer, which is worse
|
|
658
|
+
* than not seeing the producer at all.
|
|
659
|
+
*/
|
|
660
|
+
declare function readConfiguredCall(call: CallExpression, ctx: ConfiguredCallContext, spec: ConfiguredCallSpec): ConfiguredCallRead | null;
|
|
661
|
+
|
|
337
662
|
interface ContractReadResult {
|
|
338
663
|
declaredContract: RawDeclaredContract;
|
|
339
664
|
boundaryBinding: BoundaryBinding | null;
|
|
@@ -348,7 +673,7 @@ interface ContractReadResult {
|
|
|
348
673
|
* Returns null if the handler wasn't registered with a contract or the contract
|
|
349
674
|
* can't be resolved.
|
|
350
675
|
*/
|
|
351
|
-
declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern): ContractReadResult | null;
|
|
676
|
+
declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern, framework: string): ContractReadResult | null;
|
|
352
677
|
/**
|
|
353
678
|
* Resolve the contract for a consumer call site.
|
|
354
679
|
*
|
|
@@ -356,7 +681,7 @@ declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern): C
|
|
|
356
681
|
* call to find the contract, then extracts the endpoint definition for the
|
|
357
682
|
* matched method name (e.g. "getUser").
|
|
358
683
|
*/
|
|
359
|
-
declare function readContractForClientCall(callExpression: Node, methodName: string, pattern: ContractPattern): ContractReadResult | null;
|
|
684
|
+
declare function readContractForClientCall(callExpression: Node, methodName: string, pattern: ContractPattern, framework: string): ContractReadResult | null;
|
|
360
685
|
|
|
361
686
|
interface TsDiscoveryContext {
|
|
362
687
|
/** Full filesystem path of the source file. Useful for excluding
|
|
@@ -379,6 +704,32 @@ interface TsDiscoveryContext {
|
|
|
379
704
|
func: FunctionRoot;
|
|
380
705
|
isDefault: boolean;
|
|
381
706
|
}>;
|
|
707
|
+
/**
|
|
708
|
+
* For an export the project builds by calling a factory, the string a
|
|
709
|
+
* property of that call's config object holds:
|
|
710
|
+
* `export const handler = makeWidgetHandler({ subject: "a.b" }, ...)`
|
|
711
|
+
* with `{ property: "subject" }` answers `"a.b"`.
|
|
712
|
+
*
|
|
713
|
+
* Which function was called and which argument carried the config are
|
|
714
|
+
* questions the caller does not have to answer. Every object argument
|
|
715
|
+
* is read. Naming the callee or fixing the argument position narrows
|
|
716
|
+
* that, for a project whose factories would otherwise collide.
|
|
717
|
+
*
|
|
718
|
+
* Two arguments holding the property under different values answers
|
|
719
|
+
* null, since nothing says which one was meant.
|
|
720
|
+
*
|
|
721
|
+
* The config argument is usually an object literal at the call site;
|
|
722
|
+
* a variable or import is followed to the literal it resolves to.
|
|
723
|
+
* `as const` and parentheses around the property value are peeled.
|
|
724
|
+
* Anything but a string literal underneath (a computed subject, a
|
|
725
|
+
* template, a call) answers null — the caller attaches nothing
|
|
726
|
+
* rather than guessing.
|
|
727
|
+
*/
|
|
728
|
+
exportedCallConfigString(sourceFile: SourceFile, exportName: string, spec: {
|
|
729
|
+
callees?: string[];
|
|
730
|
+
argIndex?: number;
|
|
731
|
+
property: string;
|
|
732
|
+
}): string | null;
|
|
382
733
|
/**
|
|
383
734
|
* Walk a function's body for return statements whose value is a
|
|
384
735
|
* JSX element / fragment / self-closing tag. Returns true on the
|
|
@@ -391,7 +742,51 @@ interface TsDiscoveryContext {
|
|
|
391
742
|
*/
|
|
392
743
|
hasJsxReturn(func: FunctionRoot): boolean;
|
|
393
744
|
}
|
|
394
|
-
declare function createTsDiscoveryContext(): TsDiscoveryContext;
|
|
745
|
+
declare function createTsDiscoveryContext(resolution?: ResolutionStore): TsDiscoveryContext;
|
|
746
|
+
|
|
747
|
+
/** One thing that looks wrong, in the same shape the dogfood invariants report. */
|
|
748
|
+
interface HealthViolation {
|
|
749
|
+
label: string;
|
|
750
|
+
detail: string;
|
|
751
|
+
}
|
|
752
|
+
interface HealthCheck {
|
|
753
|
+
/** The property, as something that either holds or does not. */
|
|
754
|
+
name: string;
|
|
755
|
+
/**
|
|
756
|
+
* The heading printed when this check finds something.
|
|
757
|
+
*
|
|
758
|
+
* The property reads as an assertion, so printing `name` above the
|
|
759
|
+
* violations of it says "no pack collides with itself" and then lists
|
|
760
|
+
* a pack colliding with itself.
|
|
761
|
+
*/
|
|
762
|
+
whenBroken: string;
|
|
763
|
+
/**
|
|
764
|
+
* Who the finding is addressed to.
|
|
765
|
+
*
|
|
766
|
+
* A `run` check found something about the code in front of it, and
|
|
767
|
+
* the person who started the run can do something about it: drop a
|
|
768
|
+
* pack, install a dependency, open an issue with the file that
|
|
769
|
+
* broke. A `pack` check found something about how a pack was built,
|
|
770
|
+
* which only whoever ships that pack can fix. Printing the second
|
|
771
|
+
* kind on every run would teach people to skim past the first.
|
|
772
|
+
*/
|
|
773
|
+
audience: "run" | "pack";
|
|
774
|
+
violations: HealthViolation[];
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Run every health check over one extraction report and return what
|
|
778
|
+
* fired, grouped by which check caught it.
|
|
779
|
+
*/
|
|
780
|
+
declare function evaluatePackHealth(report: ExtractionReport): HealthCheck[];
|
|
781
|
+
/**
|
|
782
|
+
* The health checks that fired, as lines for a terminal.
|
|
783
|
+
*
|
|
784
|
+
* `audiences` is who the caller is printing for, and it is required
|
|
785
|
+
* because there is no answer that suits every caller: a CLI run prints
|
|
786
|
+
* what the person who started it can act on, and a run whose reader is
|
|
787
|
+
* a pack author wants both.
|
|
788
|
+
*/
|
|
789
|
+
declare function formatPackHealth(checks: ReadonlyArray<HealthCheck>, audiences: ReadonlyArray<HealthCheck["audience"]>): string;
|
|
395
790
|
|
|
396
791
|
/**
|
|
397
792
|
* Parse a ts-morph Expression into a structured Predicate.
|
|
@@ -405,6 +800,8 @@ declare function createTsDiscoveryContext(): TsDiscoveryContext;
|
|
|
405
800
|
*/
|
|
406
801
|
declare function parseConditionExpression(expr: Expression, depth?: number): Predicate | null;
|
|
407
802
|
|
|
803
|
+
declare function isImportedFrom(identifier: Node, expectedModule: string): boolean;
|
|
804
|
+
|
|
408
805
|
interface BranchFieldAccesses {
|
|
409
806
|
/** Terminal location (start, end lines) — used to match back to transitions */
|
|
410
807
|
terminalLocation: {
|
|
@@ -505,33 +902,35 @@ interface TsJsxAttributeLocation {
|
|
|
505
902
|
}
|
|
506
903
|
declare function createTsSubUnitContext(): TsSubUnitContext;
|
|
507
904
|
|
|
508
|
-
|
|
509
|
-
node: Node;
|
|
510
|
-
terminal: RawTerminal;
|
|
511
|
-
}
|
|
512
|
-
|
|
905
|
+
declare const ADAPTER_VERSION = "0.2.0";
|
|
513
906
|
/**
|
|
514
|
-
*
|
|
515
|
-
*
|
|
907
|
+
* Content hash of the given files, in the order they arrive. Empty when
|
|
908
|
+
* the list is empty or a file cannot be read, so a caller that could not
|
|
909
|
+
* place a file gets the same "no stamp" answer as a run from source
|
|
910
|
+
* rather than a hash of a shorter list.
|
|
516
911
|
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
* callback (`res.json(data)` on the unit's own `res` parameter) is still
|
|
520
|
-
* discovered. Inside a nested function only escaping terminals are
|
|
521
|
-
* matched — see `NESTED_ESCAPING_MATCH_TYPES`.
|
|
912
|
+
* The caller resolves the paths, because who a specifier resolves to
|
|
913
|
+
* depends on which package is asking.
|
|
522
914
|
*/
|
|
523
|
-
declare function
|
|
524
|
-
|
|
525
|
-
declare const ADAPTER_VERSION = "0.1.0";
|
|
915
|
+
declare function computeContentHash(paths: readonly string[]): string;
|
|
526
916
|
/**
|
|
527
917
|
* Compute a cache-friendly identity for an adapter+packs combination.
|
|
528
918
|
* Stable across processes given the same inputs; bumps when any pack
|
|
529
|
-
*
|
|
530
|
-
* adapter dist file changes (dev-mode rebuild auto-invalidation).
|
|
919
|
+
* arrives with a new version stamp, the adapter version changes, or the
|
|
920
|
+
* loaded adapter dist file changes (dev-mode rebuild auto-invalidation).
|
|
921
|
+
*
|
|
922
|
+
* A pack's stamp is whatever the caller that loaded the pack put there.
|
|
923
|
+
* The adapter takes packs as plain objects and cannot tell where one
|
|
924
|
+
* came from, so folding the config and the code into the stamp is the
|
|
925
|
+
* loader's job.
|
|
926
|
+
*
|
|
927
|
+
* A source run says so in the digest rather than leaving the adapter
|
|
928
|
+
* out of it. Nothing writes under that digest today, and a key that
|
|
929
|
+
* names the mode cannot be mistaken for a key that named the code.
|
|
531
930
|
*/
|
|
532
931
|
declare function computeAdapterPacksDigest(packVersions: ReadonlyArray<{
|
|
533
932
|
name: string;
|
|
534
933
|
version?: string;
|
|
535
934
|
}>): string;
|
|
536
935
|
|
|
537
|
-
export { ADAPTER_VERSION, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExtractionReport, type FoundTerminal, type FunctionRoot, type PackFunnel, type Timer, type TimingReport, type TsDiscoveryContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, collectAncestorBranches, collectClientFieldAccesses,
|
|
936
|
+
export { ADAPTER_VERSION, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type PackFunnel, type Timer, type TimingReport, type TsDiscoveryContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, isImportedFrom, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, resolveSubject };
|