carrick 0.3.100 → 0.3.102

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.
@@ -0,0 +1,2840 @@
1
+ /**
2
+ * `verify_library_claims` (carrick#1616) and `verify_client_semantics`
3
+ * (carrick#1564): check what a model said about a library against the
4
+ * package's own type declarations.
5
+ *
6
+ * A claim says what one export of a package does (its role, from a closed
7
+ * list) and where each part of a call through it sits: which member makes an
8
+ * instance and which option keys it reads, which member acts on the wire,
9
+ * which argument or key holds the name (a path, topic or event), the payload,
10
+ * the handler. The scanner reads call sites through a claim only when this
11
+ * check verified it, and a verified claim becomes a fact that can fail a pull
12
+ * request check. So a claim is `verified` only when the declarations say so
13
+ * positively. `failed` means the declarations resolved and contradict the
14
+ * claim; `unchecked` means they could not be read, or say nothing at the place
15
+ * the claim needs (`any`, `unknown`, `{}`, an unconstrained type parameter).
16
+ * The scanner drops both.
17
+ *
18
+ * One probe file, one set of resolution rules and one set of definitions
19
+ * ("declared", "accepts string", "says nothing") serve every role. The role
20
+ * alone picks which checks a claim needs (`ROLE_TABLE`): `http_client` keeps
21
+ * the #1564 checks and reasons exactly, so `verify_client_semantics` converts
22
+ * its checks into the shared shape (`httpCheck`) and answers through here;
23
+ * `broker`, `in_process_bus` and `socket` take the message checks, which add
24
+ * the contract's must-not-verify rules (a member only another package
25
+ * declares, a name slot beside a string slot the claim does not account for,
26
+ * a handler that says nothing). The wire is pinned on carrick#1564 (comment
27
+ * 5937606126, section 3), as contract amendment 2 (comment 5939543981)
28
+ * changes it: a maker's parts are slots, `of` is a receiver id, and a
29
+ * receiver a generic maker or scope builds is read at its declared
30
+ * type-parameter defaults.
31
+ *
32
+ * The export's type is read the way the service's own code would read it: a
33
+ * probe file in `from_dir` imports it, inside the service's program, under the
34
+ * service's compiler options. The declarations must be the installed
35
+ * package's: a `paths` alias or a `declare module` block the service writes
36
+ * for itself is not evidence about the library.
37
+ */
38
+ import * as crypto from 'node:crypto';
39
+ import * as fs from 'node:fs';
40
+ import * as path from 'node:path';
41
+ import { ts } from 'ts-morph';
42
+ /** The methods a `verb` claim may name, and a method key may accept. */
43
+ const HTTP_METHODS = new Set([
44
+ 'GET',
45
+ 'POST',
46
+ 'PUT',
47
+ 'PATCH',
48
+ 'DELETE',
49
+ 'HEAD',
50
+ 'OPTIONS',
51
+ ]);
52
+ /**
53
+ * Interfaces whose members every value of that kind inherits. A key found only
54
+ * on one of these (`constructor`, `toString`, a string's `length`) is not a
55
+ * key the library declares.
56
+ */
57
+ const BUILTIN_INTERFACES = new Set([
58
+ 'Object',
59
+ 'Function',
60
+ 'CallableFunction',
61
+ 'NewableFunction',
62
+ 'String',
63
+ 'Number',
64
+ 'Boolean',
65
+ 'Symbol',
66
+ 'BigInt',
67
+ 'Array',
68
+ 'ReadonlyArray',
69
+ ]);
70
+ /**
71
+ * The runtime's own type packages. A type they declare (the runtime's event
72
+ * emitter above all) is never part of another package's own declarations,
73
+ * even when that package's maker returns it: a member only the runtime
74
+ * declares is the runtime's, not the package's. When the named package is
75
+ * one of these, it is its own.
76
+ */
77
+ const RUNTIME_TYPE_PACKAGES = new Set([
78
+ '@types/node',
79
+ '@types/bun',
80
+ 'bun-types',
81
+ '@types/deno',
82
+ ]);
83
+ /**
84
+ * A runtime module's own specifier (`node:events`). Its declarations are the
85
+ * runtime's types package's, which is therefore its home (contract
86
+ * amendment 1, A1, on carrick#1564). The bare name (`events`) is a registry
87
+ * package's, never the runtime module.
88
+ */
89
+ const RUNTIME_MODULE = /^node:/;
90
+ /** A dependency range that names the service's own source, not a registry release. */
91
+ const LOCAL_RANGE = /^(workspace|file|link|portal):/;
92
+ /** A resolved module lands on TypeScript: a declaration file or source. */
93
+ const TYPESCRIPT_FILE = /\.(d\.[mc]?ts|[mc]?tsx?)$/;
94
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
95
+ /** The HTTP receivers of #1564: the export, or what one factory member returns. */
96
+ const HTTP_RECEIVER = /^(export|instance:\S+)$/;
97
+ const HTTP_RULES = { http: true, message: false, ops: new Set(['request']) };
98
+ const MESSAGE_RULES = {
99
+ http: false,
100
+ message: true,
101
+ ops: new Set(['send', 'receive', 'request']),
102
+ };
103
+ /**
104
+ * The role table: the role alone picks the checks. A role this build does
105
+ * not read yet (GraphQL executors, server mounts) answers `role_unsupported`,
106
+ * and `none` has nothing on the wire to check.
107
+ */
108
+ const ROLE_TABLE = {
109
+ http_client: HTTP_RULES,
110
+ broker: MESSAGE_RULES,
111
+ in_process_bus: MESSAGE_RULES,
112
+ socket: MESSAGE_RULES,
113
+ graphql_client: undefined,
114
+ server_framework: undefined,
115
+ none: undefined,
116
+ };
117
+ const VERIFIED = { verdict: 'verified' };
118
+ const failed = (reason) => ({ verdict: 'failed', reason });
119
+ const unchecked = (reason) => ({ verdict: 'unchecked', reason });
120
+ /**
121
+ * A rest parameter typed by a type variable (`...rest: A`): the element at a
122
+ * position is `A[number]`, which says nothing the predicates can read.
123
+ */
124
+ const VARIADIC = Symbol('variadic element');
125
+ function furthest(current, rank, outcome) {
126
+ if (!current || rank > current.rank)
127
+ return { rank, outcome };
128
+ if (rank === current.rank && current.outcome.verdict === 'failed' && outcome.verdict === 'unchecked') {
129
+ return { rank, outcome };
130
+ }
131
+ return current;
132
+ }
133
+ /**
134
+ * A #1564 HTTP check in the shared shape. The conversion is one to one, so
135
+ * each claim keeps its own id and its own verdict:
136
+ * - `factory` is a `make` call of `member` whose base is the base key of the
137
+ * options at argument 0;
138
+ * - `verb` is a `request` op with a fixed method and the path at argument 0;
139
+ * - `verb_body` is the same op with the body at argument 1, or with an
140
+ * options object at argument 1 and the body as one of its keys;
141
+ * - `request` is a `request` op whose url and method keys sit on the config
142
+ * at argument 0, or whose path is argument 0 and method key sits on the
143
+ * options at argument 1; `request_body` adds the body key there.
144
+ */
145
+ export function httpCheck(check) {
146
+ const common = {
147
+ claim_id: check.claim_id,
148
+ package: check.package,
149
+ export: check.export,
150
+ role: 'http_client',
151
+ receiver: check.receiver,
152
+ };
153
+ const claim = check.claim;
154
+ switch (claim.kind) {
155
+ case 'factory':
156
+ return {
157
+ ...common,
158
+ claim: { kind: 'make', form: 'call', member: claim.member, base: { arg: 0, key: claim.base_url_key } },
159
+ };
160
+ case 'verb':
161
+ return {
162
+ ...common,
163
+ claim: { kind: 'op', op: 'request', member: claim.member, method: claim.method, name: { arg: 0 } },
164
+ };
165
+ case 'verb_body':
166
+ return {
167
+ ...common,
168
+ claim: claim.args === 'path_body'
169
+ ? { kind: 'op', op: 'request', member: claim.member, name: { arg: 0 }, payload: { arg: 1 } }
170
+ : {
171
+ kind: 'op',
172
+ op: 'request',
173
+ member: claim.member,
174
+ name: { arg: 0 },
175
+ options: { arg: 1 },
176
+ ...(claim.body_key === undefined ? {} : { payload: { arg: 1, key: claim.body_key } }),
177
+ },
178
+ };
179
+ case 'request':
180
+ case 'request_body': {
181
+ const at = claim.args === 'config' ? 0 : 1;
182
+ const name = claim.args === 'config'
183
+ ? claim.url_key === undefined
184
+ ? undefined
185
+ : { arg: 0, key: claim.url_key }
186
+ : { arg: 0 };
187
+ return {
188
+ ...common,
189
+ claim: {
190
+ kind: 'op',
191
+ op: 'request',
192
+ member: claim.member,
193
+ ...(name === undefined ? {} : { name }),
194
+ method_key: { arg: at, key: claim.method_key },
195
+ ...(claim.kind === 'request_body' ? { payload: { arg: at, key: claim.body_key } } : {}),
196
+ },
197
+ };
198
+ }
199
+ }
200
+ }
201
+ let probeSequence = 0;
202
+ export class LibraryClaimsVerifier {
203
+ project;
204
+ constructor(project) {
205
+ this.project = project;
206
+ }
207
+ /**
208
+ * Judge every check, in request order, spending at most `budgetMs`. Checks
209
+ * the budget does not reach come back `unchecked` with reason `budget`.
210
+ * A check on an instance or a scope reads the maker or scope claims of the
211
+ * same request first, whatever their place in it.
212
+ */
213
+ run(fromDir, checks, budgetMs) {
214
+ const deadline = performance.now() + budgetMs;
215
+ const stamp = (check, outcome) => outcome.verdict === 'verified'
216
+ ? { claim_id: check.claim_id, receiver: check.receiver, verdict: 'verified' }
217
+ : {
218
+ claim_id: check.claim_id,
219
+ receiver: check.receiver,
220
+ verdict: outcome.verdict,
221
+ reason: outcome.reason,
222
+ };
223
+ if (checks.length === 0)
224
+ return { semantics: [], modules: [] };
225
+ // One import line per distinct (package, export), in first-seen order.
226
+ const importKeys = [];
227
+ const importIndex = new Map();
228
+ // HTTP: the base-URL keys the request's factory claims name, per factory:
229
+ // an instance is read through the overload that declares them.
230
+ const factoryKeys = new Map();
231
+ // The roles each (package, export) is given across the request.
232
+ const roles = new Map();
233
+ for (const check of checks) {
234
+ const key = exportKey(check);
235
+ if (!importIndex.has(key)) {
236
+ importIndex.set(key, importKeys.length);
237
+ importKeys.push(key);
238
+ }
239
+ const given = roles.get(key) ?? new Set();
240
+ given.add(check.role);
241
+ roles.set(key, given);
242
+ const claim = check.claim;
243
+ const baseKey = claim.kind === 'make' && claim.base?.arg === 0 ? claim.base.key : undefined;
244
+ if (check.role === 'http_client' && claim.kind === 'make' && claim.member !== null && baseKey !== undefined) {
245
+ const factory = factoryKey(key, claim.member);
246
+ const keys = factoryKeys.get(factory) ?? new Set();
247
+ keys.add(baseKey);
248
+ factoryKeys.set(factory, keys);
249
+ }
250
+ }
251
+ const importLines = importKeys.map((key, i) => {
252
+ const [pkg, name] = JSON.parse(key);
253
+ return importLine(pkg, name, `__carrick_e${i}`);
254
+ });
255
+ // One statement per receiver a message check's maker or scope claim
256
+ // builds, after the imports: that receiver built with no argument and no
257
+ // type argument. Its resolved signature is the maker (or scope) read at
258
+ // its declared type-parameter defaults (`DeclarationReader.returnOf`). An
259
+ // HTTP request adds none, so its probe is the one #1564 reads.
260
+ const builtLines = [];
261
+ const builtIndex = new Map();
262
+ for (const check of checks) {
263
+ if (!ROLE_TABLE[check.role]?.message)
264
+ continue;
265
+ const built = builtReceiver(check);
266
+ if (built === undefined)
267
+ continue;
268
+ const key = builtKey(check, built);
269
+ if (builtIndex.has(key))
270
+ continue;
271
+ const expression = receiverExpression(`__carrick_e${importIndex.get(exportKey(check))}`, built);
272
+ if (expression === undefined)
273
+ continue;
274
+ builtIndex.set(key, importLines.length + builtLines.length);
275
+ builtLines.push(`${expression};`);
276
+ }
277
+ const probeText = [...importLines, ...builtLines].join('\n');
278
+ const probePath = path.join(fromDir, `__carrick_claims_probe_${process.pid}_${probeSequence++}.ts`);
279
+ const probe = this.project.createSourceFile(probePath, `${probeText}\n`, { overwrite: true });
280
+ try {
281
+ const program = this.project.getProgram().compilerObject;
282
+ const checker = program.getTypeChecker();
283
+ const file = program.getSourceFile(probe.getFilePath());
284
+ if (!file)
285
+ throw new Error(`probe file ${probePath} is not in the program`);
286
+ const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir);
287
+ const localRanges = readLocalRanges(fromDir);
288
+ const moduleReads = new Map();
289
+ const exportReads = new Map();
290
+ const httpReceivers = new Map();
291
+ const judged = new Map();
292
+ const declarationOf = (check) => file.statements[importIndex.get(exportKey(check))];
293
+ /** The probe's no-argument build of `receiver` on the check's export, resolved. */
294
+ const atDefaults = (check, receiver) => {
295
+ const index = builtIndex.get(builtKey(check, receiver));
296
+ return index === undefined ? undefined : resolvedBuild(checker, file.statements[index]);
297
+ };
298
+ // A runtime module is read for the message roles only: HTTP answers as #1564 does.
299
+ const readModule = (check, runtimeModules) => {
300
+ const key = `${runtimeModules}\u0000${check.package}`;
301
+ let moduleRead = moduleReads.get(key);
302
+ if (!moduleRead) {
303
+ moduleRead = reader.readModule(check.package, declarationOf(check), runtimeModules);
304
+ moduleReads.set(key, moduleRead);
305
+ }
306
+ return moduleRead;
307
+ };
308
+ const readExport = (check) => {
309
+ const key = exportKey(check);
310
+ let exportRead = exportReads.get(key);
311
+ if (!exportRead) {
312
+ exportRead = reader.readExport(declarationOf(check));
313
+ exportReads.set(key, exportRead);
314
+ }
315
+ return exportRead;
316
+ };
317
+ /**
318
+ * The message receivers of check `index`, reading its maker and scope
319
+ * claims: one per distinct type the holding overloads return. A maker
320
+ * whose two overloads both hold builds either instance, so a claim on
321
+ * it must hold on each.
322
+ */
323
+ const readMessageReceivers = (index, exported) => {
324
+ const check = checks[index];
325
+ // `fitsReceiver` admitted it.
326
+ const receiverPath = parseReceiver(check.receiver);
327
+ let types = [exported.type];
328
+ let makerBindsName = false;
329
+ if (receiverPath.maker) {
330
+ const maker = receiverPath.maker;
331
+ const makers = checks
332
+ .map((other, i) => ({ other, i }))
333
+ .filter(({ other }) => other.package === check.package &&
334
+ other.export === check.export &&
335
+ other.receiver === 'export' &&
336
+ other.claim.kind === 'make' &&
337
+ other.claim.form === maker.form &&
338
+ other.claim.member === maker.member);
339
+ const made = holdingReturns(makers.map(({ i }) => judge(i)), atDefaults(check, receiverPath.base));
340
+ if ('reason' in made)
341
+ return { reason: made.reason === 'unresolved' ? 'maker_unresolved' : 'maker_unverified' };
342
+ types = made.value;
343
+ makerBindsName = makers.some(({ other }) => other.claim.kind === 'make' && other.claim.name !== undefined);
344
+ }
345
+ if (receiverPath.scope !== undefined) {
346
+ const scope = receiverPath.scope;
347
+ const scopes = checks
348
+ .map((other, i) => ({ other, i }))
349
+ .filter(({ other }) => other.package === check.package &&
350
+ other.export === check.export &&
351
+ other.receiver === receiverPath.base &&
352
+ other.claim.kind === 'scope' &&
353
+ scopeStep(other.claim) === scope);
354
+ const scoped = holdingReturns(scopes.map(({ i }) => judge(i)), atDefaults(check, check.receiver));
355
+ if ('reason' in scoped)
356
+ return { reason: scoped.reason === 'unresolved' ? 'scope_unresolved' : 'scope_unverified' };
357
+ types = scoped.value;
358
+ }
359
+ if (types.some(type => reader.returnSaysNothing(type))) {
360
+ return { reason: receiverPath.maker ? 'maker_unresolved' : 'export_untyped' };
361
+ }
362
+ return {
363
+ value: types.map(type => ({
364
+ type,
365
+ pkg: check.package,
366
+ home: reader.homeOf(check.package, [exported.target, ...typeSymbols(type)]),
367
+ makerBindsName,
368
+ scoped: receiverPath.scope !== undefined,
369
+ })),
370
+ };
371
+ };
372
+ /**
373
+ * What a set of maker (or scope) judgements build: the distinct types
374
+ * their common overloads return, each read at its declared
375
+ * type-parameter defaults where `built` (the probe's no-argument build
376
+ * of that receiver) instantiates it.
377
+ */
378
+ const holdingReturns = (judgements, built) => {
379
+ if (judgements.length === 0)
380
+ return { reason: 'unverified' };
381
+ let holding;
382
+ for (const judgement of judgements) {
383
+ if (judgement.outcome.verdict !== 'verified' || !judgement.holding)
384
+ return { reason: 'unverified' };
385
+ holding = holding === undefined ? judgement.holding : holding.filter(sig => judgement.holding.includes(sig));
386
+ }
387
+ const returns = [...new Set((holding ?? []).map(sig => reader.returnOf(sig, built)))];
388
+ if (returns.length === 0)
389
+ return { reason: 'unresolved' };
390
+ return { value: returns };
391
+ };
392
+ const judge = (index) => {
393
+ const cached = judged.get(index);
394
+ if (cached)
395
+ return cached;
396
+ // A maker read on its own instance, or a scope on itself, is a cycle.
397
+ judged.set(index, { outcome: unchecked('receiver_invalid') });
398
+ const result = judgeCheck(index);
399
+ judged.set(index, result);
400
+ return result;
401
+ };
402
+ const judgeCheck = (index) => {
403
+ const check = checks[index];
404
+ const rules = ROLE_TABLE[check.role];
405
+ if (!rules)
406
+ return { outcome: unchecked('role_unsupported') };
407
+ // An export given two roles is classified two ways; neither is a fact.
408
+ if ((roles.get(exportKey(check))?.size ?? 0) > 1)
409
+ return { outcome: unchecked('role_conflict') };
410
+ if (placementInvalid(check.claim))
411
+ return { outcome: unchecked('claim_invalid') };
412
+ if (rules.http && !HTTP_RECEIVER.test(check.receiver))
413
+ return { outcome: unchecked('receiver_invalid') };
414
+ if (rules.message && !fitsReceiver(check))
415
+ return { outcome: unchecked('receiver_invalid') };
416
+ reader.setPackage(check.package);
417
+ const moduleRead = readModule(check, rules.message);
418
+ if (moduleRead.reason)
419
+ return { outcome: unchecked(moduleRead.reason) };
420
+ if (rules.message && LOCAL_RANGE.test(localRanges.get(packageNameOf(check.package)) ?? '')) {
421
+ return { outcome: unchecked('module_workspace') };
422
+ }
423
+ const exportRead = readExport(check);
424
+ if ('reason' in exportRead)
425
+ return { outcome: unchecked(exportRead.reason) };
426
+ if (rules.http) {
427
+ const key = exportKey(check);
428
+ const receiverKey = `${key}\u0000${check.receiver}`;
429
+ let receiverRead = httpReceivers.get(receiverKey);
430
+ if (!receiverRead) {
431
+ const factory = check.receiver.startsWith('instance:') ? check.receiver.slice('instance:'.length) : undefined;
432
+ receiverRead = reader.readReceiver(exportRead.value.type, factory, factory === undefined ? undefined : factoryKeys.get(factoryKey(key, factory)));
433
+ httpReceivers.set(receiverKey, receiverRead);
434
+ }
435
+ if ('reason' in receiverRead)
436
+ return { outcome: unchecked(receiverRead.reason) };
437
+ return { outcome: reader.judgeHttp(receiverRead.value, check.claim) };
438
+ }
439
+ if (check.claim.kind === 'op' && !rules.ops.has(check.claim.op)) {
440
+ return { outcome: unchecked('role_unsupported') };
441
+ }
442
+ const receivers = readMessageReceivers(index, exportRead.value);
443
+ if ('reason' in receivers)
444
+ return { outcome: unchecked(receivers.reason) };
445
+ const built = builtReceiver(check);
446
+ const builtAtDefaults = built === undefined ? undefined : atDefaults(check, built);
447
+ const holding = [];
448
+ for (const receiver of receivers.value) {
449
+ const result = reader.judgeMessage(receiver, check.claim, builtAtDefaults);
450
+ if (result.outcome.verdict !== 'verified')
451
+ return result;
452
+ holding.push(...(result.holding ?? []));
453
+ }
454
+ return { outcome: VERIFIED, holding };
455
+ };
456
+ const semantics = [];
457
+ for (let index = 0; index < checks.length; index++) {
458
+ const check = checks[index];
459
+ if (!judged.has(index) && performance.now() >= deadline) {
460
+ semantics.push(stamp(check, unchecked('budget')));
461
+ continue;
462
+ }
463
+ semantics.push(stamp(check, judge(index).outcome));
464
+ }
465
+ return {
466
+ semantics,
467
+ modules: [...moduleReads.values()].map(read => read.entry),
468
+ };
469
+ }
470
+ finally {
471
+ this.project.removeSourceFile(probe);
472
+ }
473
+ }
474
+ /**
475
+ * Each specifier's declared surface, read with the verifier's predicates
476
+ * (carrick#1660): every value export (or only those `only` names for the
477
+ * specifier), the receivers a claim can be read on, in the verifier's
478
+ * receiver grammar, and each receiver's callable members with their
479
+ * parameter slots. A runtime module (`node:events`) is listed from the
480
+ * runtime's types package, as the message roles read it. Capped at
481
+ * `maxEntries` per specifier, with the count dropped: every export and
482
+ * receiver is listed before any member name, and every member name before
483
+ * any signature, so the cap cuts signatures first and exports last.
484
+ *
485
+ * A receiver a maker builds is listed as the verifier reads it: built with
486
+ * no argument and no type argument, so a generic maker's instance is read
487
+ * at its declared type-parameter defaults (carrick#1696). That takes two
488
+ * programs. The first reads which receivers each export can make; the
489
+ * second adds the verifier's own build statement for each
490
+ * (`receiverExpression` on the export's `importLine`, read through
491
+ * `DeclarationReader.returnOf`) and lists from it.
492
+ *
493
+ * `surface_sha256` is the full-surface hash (see `fullSurfaceSha256`).
494
+ */
495
+ listSurface(fromDir, packages, maxEntries, only = {}) {
496
+ if (packages.length === 0)
497
+ return { surfaces: [], surface_sha256: fullSurfaceSha256([]) };
498
+ // Each specifier twice: as a namespace, which holds its named exports, and
499
+ // as a default import, which is how a module that exports a value whole
500
+ // (`export =`) is imported as `default`.
501
+ const imports = packages.flatMap((pkg, i) => [
502
+ `import * as __carrick_ns${i} from ${JSON.stringify(pkg)};`,
503
+ importLine(pkg, 'default', `__carrick_d${i}`),
504
+ ]);
505
+ const probeOf = (file, i) => ({
506
+ namespace: file.statements[2 * i],
507
+ defaultImport: file.statements[2 * i + 1],
508
+ });
509
+ const makers = this.readProbe(fromDir, imports, (_checker, reader, file) => packages.map((pkg, i) => reader.makersOf(pkg, probeOf(file, i), only[pkg])));
510
+ const importLines = [];
511
+ const buildLines = [];
512
+ const builtIndex = new Map();
513
+ packages.forEach((pkg, i) => {
514
+ for (const { export: name, receivers } of makers[i]) {
515
+ const local = `__carrick_e${importLines.length}`;
516
+ importLines.push(importLine(pkg, name, local));
517
+ for (const receiver of receivers) {
518
+ const expression = receiverExpression(local, receiver);
519
+ if (expression === undefined)
520
+ continue;
521
+ builtIndex.set(builtKey({ package: pkg, export: name }, receiver), buildLines.length);
522
+ buildLines.push(`${expression};`);
523
+ }
524
+ }
525
+ });
526
+ const firstBuild = imports.length + importLines.length;
527
+ const surfaces = this.readProbe(fromDir, [...imports, ...importLines, ...buildLines], (checker, reader, file) => packages.map((pkg, i) => reader.listPackage(pkg, probeOf(file, i), maxEntries, only[pkg], (name, receiver) => {
528
+ const index = builtIndex.get(builtKey({ package: pkg, export: name }, receiver));
529
+ return index === undefined ? undefined : resolvedBuild(checker, file.statements[firstBuild + index]);
530
+ })));
531
+ return { surfaces, surface_sha256: fullSurfaceSha256(surfaces) };
532
+ }
533
+ /** `read` over a probe file holding `lines` in `fromDir`, removed again afterwards. */
534
+ readProbe(fromDir, lines, read) {
535
+ const probePath = path.join(fromDir, `__carrick_surface_probe_${process.pid}_${probeSequence++}.ts`);
536
+ const probe = this.project.createSourceFile(probePath, `${lines.join('\n')}\n`, { overwrite: true });
537
+ try {
538
+ const program = this.project.getProgram().compilerObject;
539
+ const file = program.getSourceFile(probe.getFilePath());
540
+ if (!file)
541
+ throw new Error(`probe file ${probePath} is not in the program`);
542
+ const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir);
543
+ return read(program.getTypeChecker(), reader, file);
544
+ }
545
+ finally {
546
+ this.project.removeSourceFile(probe);
547
+ }
548
+ }
549
+ }
550
+ /**
551
+ * What a probe statement that builds a receiver (`receiverExpression`)
552
+ * resolves to: its maker or scope signature as a call with no argument and
553
+ * no type argument instantiates it (see `DeclarationReader.returnOf`).
554
+ */
555
+ function resolvedBuild(checker, statement) {
556
+ if (!statement || !ts.isExpressionStatement(statement))
557
+ return undefined;
558
+ const expression = statement.expression;
559
+ if (!ts.isCallExpression(expression) && !ts.isNewExpression(expression))
560
+ return undefined;
561
+ return checker.getResolvedSignature(expression);
562
+ }
563
+ /**
564
+ * The full-surface hash the shared library store keys on: sha256 of the JSON
565
+ * array of `[package, exports]` for every surface that listed at least one
566
+ * export, sorted by `package` (code unit order). A specifier that lists
567
+ * nothing (unresolved, local, no value exports) is left out, so asking for a
568
+ * subpath a version does not have changes nothing. Type text already names
569
+ * the request's directory as `<root>`.
570
+ */
571
+ export function fullSurfaceSha256(surfaces) {
572
+ const listed = surfaces
573
+ .filter(surface => surface.reason === undefined && surface.exports.length > 0)
574
+ .map(surface => [surface.package, surface.exports])
575
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
576
+ return crypto.createHash('sha256').update(JSON.stringify(listed)).digest('hex');
577
+ }
578
+ function exportKey(check) {
579
+ return JSON.stringify([check.package, check.export]);
580
+ }
581
+ function factoryKey(exportKeyText, member) {
582
+ return `${exportKeyText}\u0000${member}`;
583
+ }
584
+ /**
585
+ * `export`, `instance:()`, `instance:new`, `instance:new:<member>` or
586
+ * `instance:<member>`, optionally followed by `>scope:<path.member>`.
587
+ */
588
+ function parseReceiver(text) {
589
+ const steps = text.split('>');
590
+ if (steps.length > 2)
591
+ return undefined;
592
+ const [base, scopeStep] = steps;
593
+ let scope;
594
+ if (scopeStep !== undefined) {
595
+ const match = /^scope:(\S+)$/.exec(scopeStep);
596
+ if (!match)
597
+ return undefined;
598
+ scope = match[1];
599
+ }
600
+ if (base === 'export')
601
+ return { base, scope };
602
+ if (base === 'instance:()')
603
+ return { base, scope, maker: { form: 'call', member: null } };
604
+ if (base === 'instance:new')
605
+ return { base, scope, maker: { form: 'new', member: null } };
606
+ let match = /^instance:new:(\S+)$/.exec(base);
607
+ if (match)
608
+ return { base, scope, maker: { form: 'new', member: match[1] } };
609
+ match = /^instance:(\S+)$/.exec(base);
610
+ if (match && match[1] !== '()' && match[1] !== 'new')
611
+ return { base, scope, maker: { form: 'call', member: match[1] } };
612
+ return undefined;
613
+ }
614
+ /** The scope step a scope claim's results are read through: its path and member, joined by `.`. */
615
+ function scopeStep(claim) {
616
+ return [...(claim.path ?? []), claim.member].join('.');
617
+ }
618
+ /**
619
+ * An op, scope or reserved name carries `on` or `of`, never both, and `of` is
620
+ * a receiver a maker or a scope builds, by its receiver id: never the export
621
+ * itself, which `on: 'export'` names (contract amendment 2, B3). "Both the
622
+ * export and one maker's instances" is two elements, not one.
623
+ */
624
+ function placementInvalid(claim) {
625
+ if (claim.kind === 'make' || claim.of === undefined)
626
+ return false;
627
+ if (claim.on !== undefined)
628
+ return true;
629
+ const of = parseReceiver(claim.of);
630
+ return of === undefined || (of.maker === undefined && of.scope === undefined);
631
+ }
632
+ /**
633
+ * A message check names a receiver its claim can be read on (contract
634
+ * amendment 2, B3). A maker is read on the export itself. An element with
635
+ * `of` is read on exactly that receiver. One with `on` is read on the export
636
+ * (`export`), on an instance of a maker (`instance`) or on either (`both`),
637
+ * and never on a receiver a scope returns, which only `of` names. One with
638
+ * neither names no receiver of its own. A claim read on a receiver it was
639
+ * not claimed for could verify a member of the same name with another shape
640
+ * (`client.write(name, value)` against `instance.write(value)`).
641
+ */
642
+ function fitsReceiver(check) {
643
+ const receiver = parseReceiver(check.receiver);
644
+ if (!receiver)
645
+ return false;
646
+ const claim = check.claim;
647
+ if (claim.kind === 'make')
648
+ return check.receiver === 'export';
649
+ if (claim.of !== undefined)
650
+ return check.receiver === claim.of;
651
+ if (claim.on === undefined)
652
+ return true;
653
+ if (receiver.scope !== undefined)
654
+ return false;
655
+ if (claim.on === 'export')
656
+ return receiver.maker === undefined;
657
+ if (claim.on === 'instance')
658
+ return receiver.maker !== undefined;
659
+ return true;
660
+ }
661
+ /**
662
+ * The receiver a maker or scope check builds: what the maker makes
663
+ * (`instance:new`), or what the scope member returns on the receiver it is
664
+ * read on (`instance:connect>scope:channel`). Undefined for an op or a
665
+ * reserved name, and for a scope on a receiver a scope already returned.
666
+ */
667
+ function builtReceiver(check) {
668
+ const claim = check.claim;
669
+ if (claim.kind === 'make') {
670
+ if (claim.member === null)
671
+ return claim.form === 'call' ? 'instance:()' : 'instance:new';
672
+ return claim.form === 'call' ? `instance:${claim.member}` : `instance:new:${claim.member}`;
673
+ }
674
+ if (claim.kind !== 'scope' || check.receiver.includes('>'))
675
+ return undefined;
676
+ return `${check.receiver}>scope:${scopeStep(claim)}`;
677
+ }
678
+ function builtKey(check, receiver) {
679
+ return JSON.stringify([check.package, check.export, receiver]);
680
+ }
681
+ /**
682
+ * What a service writes to build `receiver` on the export bound to `local`,
683
+ * passing no argument and no type argument: `new e()`, `e()`, `e.m()` or
684
+ * `new e.m()`, then `.path.member()` for a scope step. Undefined for a
685
+ * receiver id that does not parse.
686
+ */
687
+ function receiverExpression(local, receiver) {
688
+ const parsed = parseReceiver(receiver);
689
+ if (!parsed)
690
+ return undefined;
691
+ const access = (name) => (IDENTIFIER.test(name) ? `.${name}` : `[${JSON.stringify(name)}]`);
692
+ let text = local;
693
+ if (parsed.maker) {
694
+ const callee = parsed.maker.member === null ? local : `${local}${access(parsed.maker.member)}`;
695
+ text = parsed.maker.form === 'new' ? `new ${callee}()` : `${callee}()`;
696
+ }
697
+ if (parsed.scope !== undefined)
698
+ text = `${text}${parsed.scope.split('.').map(access).join('')}()`;
699
+ return text;
700
+ }
701
+ /** The symbols that say who declared a type: its own, its alias's, its parts'. */
702
+ function typeSymbols(type) {
703
+ const symbols = [type.getSymbol(), type.aliasSymbol];
704
+ if (type.isUnionOrIntersection()) {
705
+ for (const part of type.types)
706
+ symbols.push(part.getSymbol(), part.aliasSymbol);
707
+ }
708
+ return symbols;
709
+ }
710
+ /** The probe's import of one export, bound to `local`. */
711
+ function importLine(pkg, name, local) {
712
+ const specifier = JSON.stringify(pkg);
713
+ if (name === 'default')
714
+ return `import ${local} from ${specifier};`;
715
+ const imported = IDENTIFIER.test(name) ? name : JSON.stringify(name);
716
+ return `import { ${imported} as ${local} } from ${specifier};`;
717
+ }
718
+ /** Upper-case ASCII letters only: `ſ` and `ı` must not become `S` and `I`. */
719
+ function asciiUpperCase(text) {
720
+ return text.replace(/[a-z]/g, letter => letter.toUpperCase());
721
+ }
722
+ /**
723
+ * The dependency ranges of the nearest package.json at or above `fromDir`, by
724
+ * package name: a `workspace:`, `file:`, `link:` or `portal:` range names the
725
+ * service's own source, which the verifier does not check yet (R2).
726
+ */
727
+ function readLocalRanges(fromDir) {
728
+ const ranges = new Map();
729
+ let dir = path.resolve(fromDir);
730
+ for (;;) {
731
+ const manifest = path.join(dir, 'package.json');
732
+ if (fs.existsSync(manifest)) {
733
+ try {
734
+ const parsed = JSON.parse(fs.readFileSync(manifest, 'utf8'));
735
+ for (const field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
736
+ const deps = parsed[field];
737
+ if (!deps || typeof deps !== 'object')
738
+ continue;
739
+ for (const [name, range] of Object.entries(deps)) {
740
+ if (typeof range === 'string' && !ranges.has(name))
741
+ ranges.set(name, range);
742
+ }
743
+ }
744
+ }
745
+ catch {
746
+ // An unreadable manifest names no ranges.
747
+ }
748
+ return ranges;
749
+ }
750
+ const parent = path.dirname(dir);
751
+ if (parent === dir)
752
+ return ranges;
753
+ dir = parent;
754
+ }
755
+ }
756
+ /**
757
+ * Reads the declarations the probe imported, with the program's own checker.
758
+ * Every predicate is the contract's (carrick#1564, section 3), stated on the
759
+ * checker's public API, with the definitions the review amended.
760
+ */
761
+ class DeclarationReader {
762
+ program;
763
+ probe;
764
+ host;
765
+ checker;
766
+ /** The service root, as a realpath. */
767
+ root;
768
+ packageDirectories = new Map();
769
+ realpaths = new Map();
770
+ /** Names the service declares in its own blocks, per augmented package (`global` for `declare global`). */
771
+ augmentedNames;
772
+ /** The package of the check being judged; see `serviceAugmentedNames`. */
773
+ currentPackage = '';
774
+ /** `declaredProperties` per type, for `currentPackage` (its augmentations decide them). */
775
+ declared = new Map();
776
+ /** The service root as given and as a realpath, longest first: what a listing prints as `<root>`. */
777
+ printedRoots;
778
+ constructor(program, probe, host, serviceRoot) {
779
+ this.program = program;
780
+ this.probe = probe;
781
+ this.host = host;
782
+ this.checker = program.getTypeChecker();
783
+ this.root = this.realpath(serviceRoot);
784
+ this.printedRoots = [...new Set([this.root, serviceRoot])]
785
+ .filter(root => root.length > 1)
786
+ .sort((a, b) => b.length - a.length);
787
+ }
788
+ /**
789
+ * A path with its symlinks resolved. The compiler spells the service's own
790
+ * files as given and resolved dependencies as realpaths, so both sides of a
791
+ * comparison go through here. A path not on disk (the default library ts-morph
792
+ * serves from memory) is kept as it is.
793
+ */
794
+ realpath(fileName) {
795
+ let real = this.realpaths.get(fileName);
796
+ if (real === undefined) {
797
+ try {
798
+ real = fs.realpathSync(fileName);
799
+ }
800
+ catch {
801
+ real = fileName;
802
+ }
803
+ this.realpaths.set(fileName, real);
804
+ }
805
+ return real;
806
+ }
807
+ // --------------------------------------------------------------------------
808
+ // Resolve, export, receiver
809
+ // --------------------------------------------------------------------------
810
+ /**
811
+ * Where the package resolved. The checker's module symbol is what the
812
+ * service's program imports; its primary declaration must be the file the
813
+ * module resolver lands on, and the resolver must have reached it as an
814
+ * installed dependency. A `paths` alias to the service's own code, or a
815
+ * `declare module` block the service writes, stands in for the package and
816
+ * says nothing about it (`module_local`). The resolver's flag is read, not
817
+ * `program.isSourceFileFromExternalLibrary`: once ts-morph has loaded a
818
+ * dependency, the next program lists it as a root file and the program no
819
+ * longer calls it external. `resolved_file` and `installed_version` both
820
+ * come from that one agreeing answer.
821
+ */
822
+ readModule(pkg, declaration, runtimeModules = false) {
823
+ const specifier = declaration.moduleSpecifier;
824
+ const resolved = ts.resolveModuleName(pkg, this.probe.fileName, this.program.getCompilerOptions(), this.host, undefined, undefined, this.program.getModeForUsageLocation(this.probe, specifier)).resolvedModule;
825
+ const entry = { package: pkg };
826
+ const done = (reason) => reason ? { entry: { ...entry, reason }, reason } : { entry };
827
+ const moduleSymbol = this.checker.getSymbolAtLocation(specifier);
828
+ const declarations = moduleSymbol?.declarations ?? [];
829
+ if (runtimeModules && RUNTIME_MODULE.test(pkg))
830
+ return this.readRuntimeModule(declarations, entry, done);
831
+ // The module itself, not a service-side augmentation of it.
832
+ const primary = declarations.find(ts.isSourceFile) ?? declarations[0];
833
+ const file = primary?.getSourceFile();
834
+ if (file) {
835
+ entry.resolved_file = file.fileName;
836
+ const agrees = resolved !== undefined && this.isSameInstalledFile(resolved.resolvedFileName, file);
837
+ // Under `node_modules` is not enough: a `paths` alias can point the name
838
+ // at another installed package. The resolver must land in an installed
839
+ // package directory, and that package must be the one named: by its
840
+ // `packageId` (or its separate `@types` package), or, for an `npm:`
841
+ // alias, by the directory name, which the installer takes from the
842
+ // alias. (The package.json comparison beside it always holds:
843
+ // TypeScript builds `packageId` from that same package.json.)
844
+ const installed = agrees && resolved !== undefined ? this.installedPackage(resolved.resolvedFileName) : undefined;
845
+ const packageId = resolved?.packageId;
846
+ const named = installed !== undefined &&
847
+ packageId !== undefined &&
848
+ (isNamedPackage(pkg, packageId.name) ||
849
+ (installed.directoryName === packageNameOf(pkg) && installed.packageName === packageId.name));
850
+ if (named && packageId.version)
851
+ entry.installed_version = packageId.version;
852
+ if (!TYPESCRIPT_FILE.test(file.fileName))
853
+ return done('module_js_only');
854
+ if (!named)
855
+ return done('module_local');
856
+ return done();
857
+ }
858
+ if (!resolved)
859
+ return done('module_unresolved');
860
+ entry.resolved_file = resolved.resolvedFileName;
861
+ const version = resolved.packageId?.version;
862
+ if (version)
863
+ entry.installed_version = version;
864
+ if (!TYPESCRIPT_FILE.test(resolved.resolvedFileName))
865
+ return done('module_js_only');
866
+ // A declaration file with no module symbol exports nothing: every export
867
+ // of it is missing.
868
+ return done();
869
+ }
870
+ /**
871
+ * `node:<module>`: the runtime's types package declares it in an ambient
872
+ * `declare module` block, which no module resolution lands on (and which
873
+ * the checker prefers over one that does). It is the runtime's when that
874
+ * package, installed under the service, declares it; a block only the
875
+ * service writes is `module_local`.
876
+ */
877
+ readRuntimeModule(declarations, entry, done) {
878
+ if (declarations.length === 0)
879
+ return done('module_unresolved');
880
+ const ambient = declarations.find(declaration => {
881
+ const owner = this.packageOf(declaration.getSourceFile());
882
+ return owner !== undefined && RUNTIME_TYPE_PACKAGES.has(owner);
883
+ });
884
+ if (!ambient)
885
+ return done('module_local');
886
+ const file = ambient.getSourceFile();
887
+ entry.resolved_file = file.fileName;
888
+ const version = this.installedPackage(file.fileName)?.version;
889
+ if (version)
890
+ entry.installed_version = version;
891
+ return done();
892
+ }
893
+ /**
894
+ * The resolver's file is the checker's file. With two installs of the same
895
+ * name and version, TypeScript loads one and makes the other a redirect to
896
+ * it, so the resolver's copy may be a redirect whose target is the checker's
897
+ * file; that counts only when the target is itself an installed file.
898
+ */
899
+ isSameInstalledFile(resolvedFileName, file) {
900
+ const resolved = this.program.getSourceFile(resolvedFileName);
901
+ if (resolved === file)
902
+ return true;
903
+ // `redirectInfo` is internal to the compiler, but it is the only record of
904
+ // which copy TypeScript deduplicated a package into.
905
+ const target = resolved
906
+ ?.redirectInfo?.redirectTarget;
907
+ return target === file && this.isInstalledFile(file.fileName);
908
+ }
909
+ /** `T`: the type the probe's import gets, and the declaration it names. */
910
+ readExport(declaration) {
911
+ const clause = declaration.importClause;
912
+ const bindings = clause?.namedBindings;
913
+ const local = clause?.name ??
914
+ (bindings && ts.isNamedImports(bindings) ? bindings.elements[0]?.name : undefined);
915
+ const alias = local ? this.checker.getSymbolAtLocation(local) : undefined;
916
+ if (!local || !alias)
917
+ return { reason: 'export_missing' };
918
+ const target = this.checker.getAliasedSymbol(alias);
919
+ // A name the module does not export resolves to the checker's unknown
920
+ // symbol; a type-only export has no value to call.
921
+ if (this.checker.isUnknownSymbol(target) || !(target.flags & ts.SymbolFlags.Value)) {
922
+ return { reason: 'export_missing' };
923
+ }
924
+ const type = this.checker.getTypeOfSymbolAtLocation(alias, local);
925
+ if (this.isOpenTop(type))
926
+ return { reason: 'export_untyped' };
927
+ return { value: { type, target } };
928
+ }
929
+ /**
930
+ * HTTP `R`: the export itself, or what its factory `factory` returns. The
931
+ * instance comes from the first overload whose first parameter is an object
932
+ * type (or the only overload). When a `factory` claim in the request for the
933
+ * same factory holds, the overload that satisfies it must be that same one,
934
+ * or the instance is unresolved: a base URL set through one overload says
935
+ * nothing about the instance another overload builds. A factory claim that
936
+ * holds on no overload leaves the rule as it is; the scanner never reads an
937
+ * instance through it.
938
+ */
939
+ readReceiver(exported, factory, baseUrlKeys) {
940
+ if (factory === undefined)
941
+ return { value: exported };
942
+ const callable = this.callableProperty(exported, factory);
943
+ if ('failure' in callable)
944
+ return { reason: 'factory_unresolved' };
945
+ const signatures = callable.signatures;
946
+ const signature = signatures.find(sig => {
947
+ const first = this.parameterAt(sig, 0);
948
+ return first !== undefined && this.isObjectType(first);
949
+ }) ?? (signatures.length === 1 ? signatures[0] : undefined);
950
+ if (!signature)
951
+ return { reason: 'factory_unresolved' };
952
+ for (const baseUrlKey of baseUrlKeys ?? []) {
953
+ const keyed = signatures.find(sig => this.factorySignatureOutcome(sig, baseUrlKey).rank === Infinity);
954
+ if (keyed !== undefined && keyed !== signature)
955
+ return { reason: 'factory_unresolved' };
956
+ }
957
+ const instance = this.checker.getReturnTypeOfSignature(signature);
958
+ if (this.returnSaysNothing(instance))
959
+ return { reason: 'factory_unresolved' };
960
+ return { value: instance };
961
+ }
962
+ /**
963
+ * What a maker or scope signature returns. A generic one is read at its
964
+ * declared type-parameter defaults (contract amendment 2, B6) when `built`,
965
+ * the probe's build of that receiver with no argument and no type argument,
966
+ * resolved to it: TypeScript instantiates such a call at each parameter's
967
+ * default, else at its constraint when `unknown` does not satisfy it, else
968
+ * at `unknown`, which says nothing. That is what a service that passes no
969
+ * type argument gets; a type argument of its own only narrows which names
970
+ * the slots allow. Otherwise the type parameters stay open.
971
+ */
972
+ returnOf(signature, built) {
973
+ if (built !== undefined && this.instantiates(built, signature))
974
+ return this.checker.getReturnTypeOfSignature(built);
975
+ return this.checker.getReturnTypeOfSignature(signature);
976
+ }
977
+ /**
978
+ * `resolved` instantiates an overload that returns the very type
979
+ * `signature` returns: `signature` itself, or another constructor of the
980
+ * same generic class, which builds the same instance. Its type arguments
981
+ * are then `signature`'s too. Another overload's instance says nothing
982
+ * about this one's.
983
+ */
984
+ instantiates(resolved, signature) {
985
+ // `target` is internal to the compiler, but it is the only record of
986
+ // which overload a resolved call instantiated. Without it, nothing is
987
+ // read at its defaults.
988
+ const target = resolved.target;
989
+ return (target !== undefined &&
990
+ this.checker.getReturnTypeOfSignature(target) === this.checker.getReturnTypeOfSignature(signature));
991
+ }
992
+ /**
993
+ * The packages whose declarations are a receiver's own: the named package
994
+ * (and its `@types` package), and every package that declares one of
995
+ * `symbols` (the export at the end of its re-export chain, the receiver's
996
+ * type, its alias, its parts). A meta-package that re-exports a scoped core
997
+ * package's client owns that client's members; a class that extends another
998
+ * package's base does not own what it only inherits. The runtime's type
999
+ * packages and the default library never join, unless the named package is
1000
+ * one of them. `base`, when given, is the home this one extends (a
1001
+ * sub-object's, along a claim's `path`).
1002
+ */
1003
+ homeOf(pkg, symbols, base) {
1004
+ const named = packageNameOf(pkg);
1005
+ const home = new Set(base ?? [named, typesPackageOf(named)]);
1006
+ const runtime = RUNTIME_MODULE.test(pkg) || RUNTIME_TYPE_PACKAGES.has(named) || RUNTIME_TYPE_PACKAGES.has(typesPackageOf(named));
1007
+ for (const symbol of symbols) {
1008
+ for (const declaration of symbol?.declarations ?? []) {
1009
+ const owner = this.packageOf(declaration.getSourceFile());
1010
+ if (owner === undefined)
1011
+ continue;
1012
+ if (!runtime && RUNTIME_TYPE_PACKAGES.has(owner))
1013
+ continue;
1014
+ home.add(owner);
1015
+ }
1016
+ }
1017
+ return home;
1018
+ }
1019
+ /** The installed package a file belongs to, by its package.json name; never the default library. */
1020
+ packageOf(file) {
1021
+ if (this.program.isSourceFileDefaultLibrary(file))
1022
+ return undefined;
1023
+ const installed = this.installedPackage(file.fileName);
1024
+ return installed === undefined ? undefined : installed.packageName ?? installed.directoryName;
1025
+ }
1026
+ isOwnFile(file, home) {
1027
+ const owner = this.packageOf(file);
1028
+ return owner !== undefined && home.has(owner);
1029
+ }
1030
+ // --------------------------------------------------------------------------
1031
+ // HTTP claims (#1564, unchanged)
1032
+ // --------------------------------------------------------------------------
1033
+ /**
1034
+ * An HTTP claim in the shared shape, judged by the #1564 check its shape
1035
+ * came from (`httpCheck` is the inverse). A shape no #1564 kind produces is
1036
+ * `claim_invalid`.
1037
+ */
1038
+ judgeHttp(receiver, claim) {
1039
+ if (claim.kind === 'make') {
1040
+ const base = claim.base;
1041
+ if (claim.form !== 'call' ||
1042
+ claim.member === null ||
1043
+ claim.name !== undefined ||
1044
+ claim.handler !== undefined ||
1045
+ claim.prefix !== undefined ||
1046
+ base === undefined ||
1047
+ base.arg !== 0 ||
1048
+ base.key === undefined) {
1049
+ return unchecked('claim_invalid');
1050
+ }
1051
+ return this.judgeFactory(receiver, claim.member, base.key);
1052
+ }
1053
+ if (claim.kind !== 'op' || claim.op !== 'request' || claim.handler || claim.ack || claim.path !== undefined) {
1054
+ return unchecked('claim_invalid');
1055
+ }
1056
+ const name = claim.name;
1057
+ if (name !== undefined && 'bound' in name)
1058
+ return unchecked('claim_invalid');
1059
+ if (claim.method_key) {
1060
+ const at = claim.method_key.arg;
1061
+ const methodKey = claim.method_key.key;
1062
+ if (methodKey === undefined || claim.method !== undefined || claim.options !== undefined) {
1063
+ return unchecked('claim_invalid');
1064
+ }
1065
+ const payload = claim.payload;
1066
+ if (at === 0) {
1067
+ if ((name && (name.arg !== 0 || name.key === undefined)) || (payload && (payload.arg !== 0 || payload.key === undefined))) {
1068
+ return unchecked('claim_invalid');
1069
+ }
1070
+ const selected = this.selectRequest(receiver, claim.member, 'config', name?.key, methodKey, payload?.key);
1071
+ return 'failure' in selected ? selected.failure : VERIFIED;
1072
+ }
1073
+ if (at === 1) {
1074
+ if (!name || name.arg !== 0 || name.key !== undefined || (payload && (payload.arg !== 1 || payload.key === undefined))) {
1075
+ return unchecked('claim_invalid');
1076
+ }
1077
+ const selected = this.selectRequest(receiver, claim.member, 'path_options', undefined, methodKey, payload?.key);
1078
+ return 'failure' in selected ? selected.failure : VERIFIED;
1079
+ }
1080
+ return unchecked('claim_invalid');
1081
+ }
1082
+ if (claim.member === null || !name || name.arg !== 0 || name.key !== undefined)
1083
+ return unchecked('claim_invalid');
1084
+ const hasBody = claim.payload !== undefined || claim.options !== undefined;
1085
+ if (claim.method === undefined && !hasBody)
1086
+ return unchecked('claim_invalid');
1087
+ if (claim.method !== undefined) {
1088
+ const verb = this.judgeVerb(receiver, claim.member, claim.method);
1089
+ if (verb.verdict !== 'verified' || !hasBody)
1090
+ return verb;
1091
+ }
1092
+ if (claim.options !== undefined) {
1093
+ const payload = claim.payload;
1094
+ if (claim.options.arg !== 1 || claim.options.key !== undefined || (payload && (payload.arg !== 1 || payload.key === undefined))) {
1095
+ return unchecked('claim_invalid');
1096
+ }
1097
+ return this.judgeVerbBody(receiver, claim.member, 'path_options', payload?.key);
1098
+ }
1099
+ if (claim.payload.arg !== 1 || claim.payload.key !== undefined)
1100
+ return unchecked('claim_invalid');
1101
+ return this.judgeVerbBody(receiver, claim.member, 'path_body', undefined);
1102
+ }
1103
+ /**
1104
+ * `member` is a declared callable property of the receiver; some signature's
1105
+ * first parameter declares `base_url_key` accepting string, and returns a
1106
+ * type that says something.
1107
+ */
1108
+ judgeFactory(receiver, member, baseUrlKey) {
1109
+ const callable = this.callableProperty(receiver, member);
1110
+ if ('failure' in callable)
1111
+ return callable.failure;
1112
+ let best;
1113
+ for (const signature of callable.signatures) {
1114
+ const result = this.factorySignatureOutcome(signature, baseUrlKey);
1115
+ if (result.rank === Infinity)
1116
+ return VERIFIED;
1117
+ best = furthest(best, result.rank, result.outcome);
1118
+ }
1119
+ return best.outcome;
1120
+ }
1121
+ /** One factory overload against the factory predicate; rank `Infinity` holds. */
1122
+ factorySignatureOutcome(signature, baseUrlKey) {
1123
+ const first = this.keyParameterAt(signature, 0);
1124
+ if (first === undefined)
1125
+ return { rank: 1, outcome: failed('param_missing') };
1126
+ const key = this.keyProperty(first, baseUrlKey, type => this.acceptsString(type));
1127
+ if (!key)
1128
+ return { rank: 2, outcome: this.slotFailure(first, 'key_missing') };
1129
+ const keyType = this.checker.getTypeOfSymbol(key);
1130
+ if (!this.acceptsString(keyType))
1131
+ return { rank: 3, outcome: this.slotFailure(keyType, 'key_not_string') };
1132
+ // The option is there, but what the factory builds cannot be read, so
1133
+ // nothing it returns can be checked either.
1134
+ if (this.returnSaysNothing(this.checker.getReturnTypeOfSignature(signature))) {
1135
+ return { rank: 4, outcome: unchecked('factory_unresolved') };
1136
+ }
1137
+ return { rank: Infinity, outcome: VERIFIED };
1138
+ }
1139
+ /**
1140
+ * `method` is `member` upper-cased (ASCII only) and an HTTP method (a method
1141
+ * is not a type-level fact, so this is read off the claim itself); `member`
1142
+ * is a declared callable property whose first parameter accepts string.
1143
+ */
1144
+ judgeVerb(receiver, member, method) {
1145
+ if (method !== asciiUpperCase(member) || !HTTP_METHODS.has(method)) {
1146
+ return failed('method_not_member_verb');
1147
+ }
1148
+ const callable = this.callableProperty(receiver, member);
1149
+ if ('failure' in callable)
1150
+ return callable.failure;
1151
+ let best;
1152
+ for (const signature of callable.signatures) {
1153
+ const first = this.keyParameterAt(signature, 0);
1154
+ if (first !== undefined && this.acceptsString(first))
1155
+ return VERIFIED;
1156
+ best = furthest(best, 1, first === undefined ? failed('path_not_string') : this.slotFailure(first, 'path_not_string'));
1157
+ }
1158
+ return best.outcome;
1159
+ }
1160
+ /**
1161
+ * Some signature whose first parameter accepts string has a second one:
1162
+ * open for `path_body`; for `path_options`, an object type with at least one
1163
+ * declared property, `body_key` among them when given.
1164
+ */
1165
+ judgeVerbBody(receiver, member, args, bodyKey) {
1166
+ const callable = this.callableProperty(receiver, member);
1167
+ if ('failure' in callable)
1168
+ return callable.failure;
1169
+ let best;
1170
+ for (const signature of callable.signatures) {
1171
+ const first = this.keyParameterAt(signature, 0);
1172
+ // An open body is read as declared: a type parameter is the open body
1173
+ // itself, not its constraint.
1174
+ const second = args === 'path_body' ? this.parameterAt(signature, 1) : this.keyParameterAt(signature, 1);
1175
+ if (first === undefined || second === undefined) {
1176
+ best = furthest(best, 1, failed('param_missing'));
1177
+ continue;
1178
+ }
1179
+ if (!this.acceptsString(first)) {
1180
+ best = furthest(best, 1, this.slotFailure(first, 'param_missing'));
1181
+ continue;
1182
+ }
1183
+ if (args === 'path_body') {
1184
+ if (this.isOpenBody(second))
1185
+ return VERIFIED;
1186
+ best = furthest(best, 2, this.slotFailure(second, 'body_not_open'));
1187
+ continue;
1188
+ }
1189
+ if (!this.isObjectType(second) || this.declaredProperties(second).length === 0) {
1190
+ best = furthest(best, 2, this.slotFailure(second, 'options_not_object'));
1191
+ continue;
1192
+ }
1193
+ if (bodyKey !== undefined && !this.declaredProperty(second, bodyKey)) {
1194
+ best = furthest(best, 3, failed('key_missing'));
1195
+ continue;
1196
+ }
1197
+ return VERIFIED;
1198
+ }
1199
+ return best.outcome;
1200
+ }
1201
+ /**
1202
+ * The callee's signatures a request claim can be read through. The callee
1203
+ * is `receiver[member]`, or the receiver itself when `member` is null.
1204
+ *
1205
+ * `config`: the first parameter is an object type declaring `url_key`
1206
+ * accepting string and `method_key` accepting string or an HTTP method
1207
+ * literal. `path_options`: the first parameter accepts string and the second
1208
+ * is an object type declaring `method_key` accepting the same. A
1209
+ * `request_body` claim also needs `body_key` declared there.
1210
+ *
1211
+ * All the keys come from ONE config object: for a union, from one member
1212
+ * that is an object type and not a function type. Keys split across union
1213
+ * members (`{ url } | { method }`) describe no call anyone can make.
1214
+ */
1215
+ selectRequest(receiver, member, args, urlKey, methodKey, bodyKey) {
1216
+ const callable = member === null ? this.callSignatures(receiver) : this.callableProperty(receiver, member);
1217
+ if ('failure' in callable)
1218
+ return callable;
1219
+ const selected = [];
1220
+ let best;
1221
+ for (const signature of callable.signatures) {
1222
+ const first = this.keyParameterAt(signature, 0);
1223
+ let config = first;
1224
+ if (args === 'path_options') {
1225
+ if (first === undefined || !this.acceptsString(first)) {
1226
+ best = furthest(best, 1, first === undefined ? failed('param_missing') : this.slotFailure(first, 'param_missing'));
1227
+ continue;
1228
+ }
1229
+ config = this.keyParameterAt(signature, 1);
1230
+ }
1231
+ if (config === undefined) {
1232
+ best = furthest(best, 1, failed('param_missing'));
1233
+ continue;
1234
+ }
1235
+ if (config === VARIADIC || !this.isObjectType(config)) {
1236
+ best = furthest(best, 1, this.slotFailure(config, 'param_missing'));
1237
+ continue;
1238
+ }
1239
+ const parts = this.parts(config);
1240
+ const views = parts.length > 1 ? parts.filter(part => this.isPlainObject(part)) : [config];
1241
+ if (views.length === 0)
1242
+ best = furthest(best, 2, failed('key_missing'));
1243
+ for (const view of views) {
1244
+ const outcome = this.requestKeysOutcome(view, args, urlKey, methodKey, bodyKey);
1245
+ if (outcome.rank === Infinity) {
1246
+ selected.push(signature);
1247
+ break;
1248
+ }
1249
+ best = furthest(best, outcome.rank, outcome.outcome);
1250
+ }
1251
+ }
1252
+ if (selected.length > 0)
1253
+ return { signatures: selected };
1254
+ return { failure: best.outcome };
1255
+ }
1256
+ /** One config object against a request claim's keys; rank `Infinity` holds. */
1257
+ requestKeysOutcome(config, args, urlKey, methodKey, bodyKey) {
1258
+ // A `config` claim names its url key; with none there is nothing to find.
1259
+ const url = args === 'config'
1260
+ ? urlKey === undefined
1261
+ ? undefined
1262
+ : this.declaredProperty(config, urlKey)
1263
+ : null;
1264
+ const method = this.declaredProperty(config, methodKey);
1265
+ if (url === undefined || !method)
1266
+ return { rank: 2, outcome: failed('key_missing') };
1267
+ const urlType = url === null ? undefined : this.checker.getTypeOfSymbol(url);
1268
+ if (urlType !== undefined && !this.acceptsString(urlType)) {
1269
+ return { rank: 3, outcome: this.slotFailure(urlType, 'key_not_string') };
1270
+ }
1271
+ const methodType = this.checker.getTypeOfSymbol(method);
1272
+ if (!this.acceptsMethod(methodType)) {
1273
+ return { rank: 3, outcome: this.slotFailure(methodType, 'key_not_string') };
1274
+ }
1275
+ if (bodyKey !== undefined && !this.declaredProperty(config, bodyKey)) {
1276
+ return { rank: 4, outcome: failed('key_missing') };
1277
+ }
1278
+ return { rank: Infinity, outcome: VERIFIED };
1279
+ }
1280
+ // --------------------------------------------------------------------------
1281
+ // Message claims (broker, in-process bus, socket)
1282
+ // --------------------------------------------------------------------------
1283
+ /**
1284
+ * A message claim on its receiver; a maker or scope claim also returns the
1285
+ * overloads it holds on. `built` is the probe's no-argument build of the
1286
+ * receiver a maker or scope claim makes (see `returnOf`).
1287
+ */
1288
+ judgeMessage(receiver, claim, built) {
1289
+ switch (claim.kind) {
1290
+ case 'make':
1291
+ return this.judgeMake(receiver, claim, built);
1292
+ case 'scope':
1293
+ return this.judgeScope(receiver, claim, built);
1294
+ case 'op':
1295
+ return { outcome: this.judgeOp(receiver, claim) };
1296
+ case 'reserved':
1297
+ return { outcome: this.judgeReserved(receiver, claim) };
1298
+ }
1299
+ }
1300
+ /**
1301
+ * `member` (or the export itself, when null) is callable (`call`) or
1302
+ * constructible (`new`) through signatures the receiver's home packages
1303
+ * declare; some overload takes the base, prefix, name and handler where the
1304
+ * claim's slots put them (contract amendment 2, B2), under the name-slot
1305
+ * rules, and returns a type that says something, read at its type-parameter
1306
+ * defaults (`returnOf`). Those overloads are the ones an instance is read
1307
+ * through. A definition (`task({ id, run })`) is a maker with a `name` and a
1308
+ * `handler` slot; a queue (`new Queue("emails")`) one with a positional name.
1309
+ */
1310
+ judgeMake(receiver, claim, built) {
1311
+ const layout = this.layout({ base: claim.base, prefix: claim.prefix, name: claim.name, handler: claim.handler });
1312
+ if ('failure' in layout)
1313
+ return { outcome: layout.failure };
1314
+ const callee = this.ownCallee(receiver, [], claim.member, claim.form);
1315
+ if ('failure' in callee)
1316
+ return { outcome: callee.failure };
1317
+ const labels = claim.key_labels ?? {};
1318
+ const holding = [];
1319
+ let best;
1320
+ for (const signature of callee.signatures) {
1321
+ let result = this.messageSignatureOutcome(signature, layout.value, labels);
1322
+ if (result.rank === Infinity && this.returnSaysNothing(this.returnOf(signature, built))) {
1323
+ result = { rank: 6, outcome: unchecked('maker_unresolved') };
1324
+ }
1325
+ if (result.rank === Infinity)
1326
+ holding.push(signature);
1327
+ else
1328
+ best = furthest(best, result.rank, result.outcome);
1329
+ }
1330
+ if (holding.length > 0)
1331
+ return { outcome: VERIFIED, holding };
1332
+ return { outcome: best.outcome };
1333
+ }
1334
+ /**
1335
+ * `member` (after `path`) is a home-declared callable member whose name
1336
+ * slot accepts a string, under the name-slot rules, and returns a type that
1337
+ * says something, read at its type-parameter defaults (`returnOf`).
1338
+ */
1339
+ judgeScope(receiver, claim, built) {
1340
+ const layout = this.layout({ name: claim.name });
1341
+ if ('failure' in layout)
1342
+ return { outcome: layout.failure };
1343
+ const callee = this.ownCallee(receiver, claim.path ?? [], claim.member, 'call');
1344
+ if ('failure' in callee)
1345
+ return { outcome: callee.failure };
1346
+ const labels = claim.key_labels ?? {};
1347
+ const holding = [];
1348
+ let best;
1349
+ for (const signature of callee.signatures) {
1350
+ let result = this.messageSignatureOutcome(signature, layout.value, labels);
1351
+ if (result.rank === Infinity && this.returnSaysNothing(this.returnOf(signature, built))) {
1352
+ result = { rank: 6, outcome: unchecked('scope_unresolved') };
1353
+ }
1354
+ if (result.rank === Infinity)
1355
+ holding.push(signature);
1356
+ else
1357
+ best = furthest(best, result.rank, result.outcome);
1358
+ }
1359
+ if (holding.length > 0)
1360
+ return { outcome: VERIFIED, holding };
1361
+ return { outcome: best.outcome };
1362
+ }
1363
+ /**
1364
+ * A send, receive or request op: the home-declared callable member (after
1365
+ * `path`; or the receiver itself, when null) with some overload that takes
1366
+ * the name, payload, handler and acknowledgement where the claim puts them.
1367
+ * A name bound by the maker or the scope must have been bound: the
1368
+ * instance's maker claim has a `name` slot, or the receiver came through a
1369
+ * scope.
1370
+ */
1371
+ judgeOp(receiver, claim) {
1372
+ if (claim.method !== undefined || claim.method_key !== undefined || claim.options !== undefined) {
1373
+ return unchecked('claim_invalid');
1374
+ }
1375
+ const name = claim.name;
1376
+ // A send carries a payload: a name alone on `send(data)` is the data.
1377
+ if (name === undefined || (claim.op === 'send' && claim.payload === undefined))
1378
+ return unchecked('claim_invalid');
1379
+ let nameSlot;
1380
+ if ('bound' in name) {
1381
+ if (name.bound === 'maker' ? !receiver.makerBindsName : !receiver.scoped)
1382
+ return failed('name_unbound');
1383
+ }
1384
+ else {
1385
+ nameSlot = name;
1386
+ }
1387
+ const layout = this.layout({ name: nameSlot, payload: claim.payload, handler: claim.handler, ack: claim.ack });
1388
+ if ('failure' in layout)
1389
+ return layout.failure;
1390
+ const callee = this.ownCallee(receiver, claim.path ?? [], claim.member, 'call');
1391
+ if ('failure' in callee)
1392
+ return callee.failure;
1393
+ const labels = claim.key_labels ?? {};
1394
+ let best;
1395
+ for (const signature of callee.signatures) {
1396
+ const result = this.messageSignatureOutcome(signature, layout.value, labels);
1397
+ if (result.rank === Infinity)
1398
+ return VERIFIED;
1399
+ best = furthest(best, result.rank, result.outcome);
1400
+ }
1401
+ return best.outcome;
1402
+ }
1403
+ /**
1404
+ * The declarations spell `name` in one of `member`'s parameters: an
1405
+ * overload whose parameter there is that literal, or a key of the map a
1406
+ * `keyof` constraint reads. The claim carries no slot, so every parameter
1407
+ * is read. A name the library emits itself only ever removes rows, so this
1408
+ * verdict is for the record.
1409
+ */
1410
+ judgeReserved(receiver, claim) {
1411
+ const callee = this.ownCallee(receiver, claim.path ?? [], claim.member, 'call');
1412
+ if ('failure' in callee)
1413
+ return callee.failure;
1414
+ for (const signature of callee.signatures) {
1415
+ const count = signature.getParameters().length;
1416
+ for (let arg = 0; arg < count; arg++) {
1417
+ if (this.literalsAt(signature, { arg }).has(claim.name))
1418
+ return VERIFIED;
1419
+ }
1420
+ }
1421
+ return failed('reserved_not_declared');
1422
+ }
1423
+ /**
1424
+ * Where a claim's parts sit. Two parts at one position (`send(data)` read
1425
+ * as both the name and the payload), or a part at a position another part
1426
+ * reads keys of, describe no call: `slots_overlap`.
1427
+ */
1428
+ layout(parts) {
1429
+ const positional = new Map();
1430
+ const keyed = new Map();
1431
+ for (const [part, slot] of Object.entries(parts)) {
1432
+ if (slot === undefined)
1433
+ continue;
1434
+ if (slot.key === undefined) {
1435
+ if (positional.has(slot.arg) || keyed.has(slot.arg))
1436
+ return { failure: failed('slots_overlap') };
1437
+ positional.set(slot.arg, part);
1438
+ continue;
1439
+ }
1440
+ if (positional.has(slot.arg))
1441
+ return { failure: failed('slots_overlap') };
1442
+ const keys = keyed.get(slot.arg) ?? new Map();
1443
+ if (keys.has(slot.key))
1444
+ return { failure: failed('slots_overlap') };
1445
+ keys.set(slot.key, part);
1446
+ keyed.set(slot.arg, keys);
1447
+ }
1448
+ return { value: { positional, keyed } };
1449
+ }
1450
+ /**
1451
+ * One overload against a message claim's layout; rank `Infinity` holds.
1452
+ *
1453
+ * 1. A positional name, base or prefix accepts a string.
1454
+ * 2. A positional name is not a key of an index-signature map; a payload
1455
+ * is there (its type is not read) and is not a callback.
1456
+ * 3. A positional handler or acknowledgement is a function type with a
1457
+ * declared signature. Keyed parts: the object at that argument declares
1458
+ * every claimed key in one view (one union member), a name, base or
1459
+ * prefix key accepting a string and a handler key a declared function.
1460
+ * 4. Every string slot of the call besides the name is accounted for
1461
+ * (design D2, `nameSiblings`).
1462
+ */
1463
+ messageSignatureOutcome(signature, layout, labels) {
1464
+ for (const [arg, part] of layout.positional) {
1465
+ if (part !== 'name' && part !== 'base' && part !== 'prefix')
1466
+ continue;
1467
+ const declared = this.keyParameterAt(signature, arg);
1468
+ if (declared === undefined)
1469
+ return { rank: 1, outcome: failed('param_missing') };
1470
+ const slot = this.throughConditional(declared);
1471
+ if (!this.acceptsString(slot)) {
1472
+ return { rank: 1, outcome: this.slotFailure(slot, part === 'name' ? 'name_not_string' : 'slot_not_string') };
1473
+ }
1474
+ }
1475
+ for (const [arg, part] of layout.positional) {
1476
+ if (part === 'name' && this.isIndexKeySlot(signature, arg)) {
1477
+ return { rank: 2, outcome: failed('name_index_key') };
1478
+ }
1479
+ if (part === 'payload') {
1480
+ const payload = this.parameterAt(signature, arg);
1481
+ if (payload === undefined)
1482
+ return { rank: 2, outcome: failed('payload_missing') };
1483
+ // Function versus data is shape: a callback in that place is not the payload.
1484
+ if (this.isFunctionSlot(payload))
1485
+ return { rank: 2, outcome: failed('payload_is_function') };
1486
+ }
1487
+ }
1488
+ for (const [arg, part] of layout.positional) {
1489
+ if (part !== 'handler' && part !== 'ack')
1490
+ continue;
1491
+ const failure = this.handlerFailure(this.keyParameterAt(signature, arg));
1492
+ if (failure)
1493
+ return { rank: 3, outcome: failure };
1494
+ }
1495
+ const views = new Map();
1496
+ for (const [arg, keys] of layout.keyed) {
1497
+ const slot = this.keyParameterAt(signature, arg);
1498
+ if (slot === undefined)
1499
+ return { rank: 1, outcome: failed('param_missing') };
1500
+ const view = this.viewFor(slot, keys);
1501
+ if ('rank' in view)
1502
+ return view;
1503
+ views.set(arg, view.view);
1504
+ }
1505
+ const ambiguity = this.nameSiblings(signature, layout, views, labels);
1506
+ if (ambiguity)
1507
+ return { rank: 4, outcome: ambiguity };
1508
+ return { rank: Infinity, outcome: VERIFIED };
1509
+ }
1510
+ /**
1511
+ * The object at a keyed argument, read as one view: the whole type, or one
1512
+ * union member that is an object type and not a function type, declaring
1513
+ * every claimed key with the type its part needs.
1514
+ */
1515
+ viewFor(slot, keys) {
1516
+ if (slot === VARIADIC || !this.isObjectType(slot)) {
1517
+ return { rank: 1, outcome: this.slotFailure(slot, 'param_missing') };
1518
+ }
1519
+ const views = this.keyViews(slot);
1520
+ let best;
1521
+ if (views.length === 0)
1522
+ best = { rank: 2, outcome: failed('key_missing') };
1523
+ for (const view of views) {
1524
+ let failure;
1525
+ for (const [key, part] of keys) {
1526
+ const property = this.declaredProperty(view, key);
1527
+ if (!property) {
1528
+ failure = { rank: 2, outcome: failed('key_missing') };
1529
+ break;
1530
+ }
1531
+ const type = this.checker.getTypeOfSymbol(property);
1532
+ if ((part === 'name' || part === 'base' || part === 'prefix') && !this.acceptsString(this.throughConditional(type))) {
1533
+ failure = { rank: 3, outcome: this.slotFailure(this.throughConditional(type), 'key_not_string') };
1534
+ break;
1535
+ }
1536
+ if (part === 'payload' && this.isFunctionSlot(type)) {
1537
+ failure = { rank: 3, outcome: failed('payload_is_function') };
1538
+ break;
1539
+ }
1540
+ if (part === 'handler' || part === 'ack') {
1541
+ const bad = this.handlerFailure(type);
1542
+ if (bad) {
1543
+ failure = { rank: 3, outcome: bad };
1544
+ break;
1545
+ }
1546
+ }
1547
+ }
1548
+ if (!failure)
1549
+ return { view };
1550
+ best = furthest(best, failure.rank, failure.outcome);
1551
+ }
1552
+ return best;
1553
+ }
1554
+ /**
1555
+ * The objects a claim's keys at one argument are read from, one at a time:
1556
+ * the whole type, or each union member that is an object type and not a
1557
+ * function type.
1558
+ */
1559
+ keyViews(slot) {
1560
+ const parts = this.parts(slot);
1561
+ return parts.length > 1 ? parts.filter(part => this.isPlainObject(part)) : [slot];
1562
+ }
1563
+ /**
1564
+ * Strict D2: `name_ambiguous` unless every string slot of the call besides
1565
+ * the name is accounted for, because which of two string slots is the name
1566
+ * is behaviour, not shape. A slot is accounted for when the claim assigns it
1567
+ * a part (payload, handler, base, prefix), or, for a key of an object at
1568
+ * the call, when `key_labels` labels it `not_name`. A positional string the
1569
+ * claim leaves unassigned cannot be labelled, so it always competes. A
1570
+ * slot or key counts as a string exactly when a name there would
1571
+ * (`siblingTakesString`): a conditional counts when one of its branches
1572
+ * takes a string (fail closed, carrick#1687).
1573
+ *
1574
+ * The labels must agree with the claim: at most one key is labelled `name`,
1575
+ * and only the claim's own name key; that key is never labelled `not_name`.
1576
+ * A `not_name` label for a key an overload does not declare is ignored
1577
+ * (one label map serves every overload). No name, no rule.
1578
+ *
1579
+ * Rest parameters: a rest the claim puts a payload or handler in belongs
1580
+ * wholly to that part; one that holds the name holds other names too; from
1581
+ * the first variable element of a tuple rest (`[...channels: string[], cb]`)
1582
+ * no position is fixed, so a name there is one of many. A rest the claim
1583
+ * leaves unassigned competes when its element accepts a string, or when it
1584
+ * cannot be read at all (`...args: Rest<B>`, a conditional on a type
1585
+ * parameter), since it could hold a string slot.
1586
+ */
1587
+ nameSiblings(signature, layout, views, labels) {
1588
+ const namedAt = [...layout.positional].find(([, part]) => part === 'name')?.[0];
1589
+ let nameKey;
1590
+ for (const keys of layout.keyed.values()) {
1591
+ for (const [key, part] of keys)
1592
+ if (part === 'name')
1593
+ nameKey = key;
1594
+ }
1595
+ if (namedAt === undefined && nameKey === undefined)
1596
+ return undefined;
1597
+ const ambiguous = failed('name_ambiguous');
1598
+ const nameLabels = Object.keys(labels).filter(key => labels[key] === 'name');
1599
+ if (nameLabels.length > 1)
1600
+ return ambiguous;
1601
+ if (nameLabels.length === 1 && nameLabels[0] !== nameKey)
1602
+ return ambiguous;
1603
+ if (nameKey !== undefined && labels[nameKey] === 'not_name')
1604
+ return ambiguous;
1605
+ // A string key at the call the claim neither assigns nor labels.
1606
+ const unaccountedKey = (keys, assigned) => keys.some(key => !assigned.has(key) && labels[key] !== 'not_name');
1607
+ const none = new Map();
1608
+ const takesString = (slot) => slot !== undefined && this.siblingTakesString(slot);
1609
+ // An argument the claim gives no part: a string, an object with a string
1610
+ // key nobody accounts for, or a rest the verifier cannot read, which could
1611
+ // hold either and no label can account for (fail closed, carrick#1687).
1612
+ const competes = (slot) => slot === VARIADIC || takesString(slot) || unaccountedKey(this.stringKeys(slot), none);
1613
+ // Every object the claim reads keys of: its other string keys.
1614
+ for (const [arg, keys] of layout.keyed) {
1615
+ const view = views.get(arg);
1616
+ if (view && unaccountedKey(this.stringKeys(view), keys))
1617
+ return ambiguous;
1618
+ }
1619
+ const parameters = signature.getParameters();
1620
+ const last = parameters[parameters.length - 1];
1621
+ const lastDeclaration = last?.valueDeclaration;
1622
+ const restIndex = lastDeclaration && ts.isParameter(lastDeclaration) && lastDeclaration.dotDotDotToken ? parameters.length - 1 : -1;
1623
+ const fixed = restIndex === -1 ? parameters.length : restIndex;
1624
+ const assigned = (i) => layout.positional.has(i) || layout.keyed.has(i);
1625
+ for (let i = 0; i < fixed; i++) {
1626
+ if (assigned(i))
1627
+ continue;
1628
+ if (competes(this.keyParameterAt(signature, i)))
1629
+ return ambiguous;
1630
+ }
1631
+ if (restIndex === -1)
1632
+ return undefined;
1633
+ const rest = this.checker.getTypeOfSymbol(last);
1634
+ if (this.checker.isTupleType(rest)) {
1635
+ const elements = this.checker.getTypeArguments(rest);
1636
+ const flags = rest.target.elementFlags;
1637
+ const variable = flags.findIndex(flag => (flag & ts.ElementFlags.Variable) !== 0);
1638
+ const fixedLength = variable === -1 ? elements.length : variable;
1639
+ for (let i = restIndex; i < restIndex + fixedLength; i++) {
1640
+ if (assigned(i))
1641
+ continue;
1642
+ if (competes(this.keyParameterAt(signature, i)))
1643
+ return ambiguous;
1644
+ }
1645
+ if (variable !== -1) {
1646
+ const unfixed = [...layout.positional].filter(([arg]) => arg >= restIndex + variable).map(([, part]) => part);
1647
+ if (unfixed.includes('name'))
1648
+ return ambiguous;
1649
+ const element = elements[variable];
1650
+ const each = element !== undefined && this.checker.isArrayType(element)
1651
+ ? this.checker.getTypeArguments(element)[0]
1652
+ : element;
1653
+ if (unfixed.length === 0 && competes(each === undefined ? undefined : this.throughConstraint(each))) {
1654
+ return ambiguous;
1655
+ }
1656
+ }
1657
+ return undefined;
1658
+ }
1659
+ const inRest = [...layout.positional].filter(([arg]) => arg >= restIndex).map(([, part]) => part);
1660
+ if (inRest.includes('name'))
1661
+ return takesString(this.keyParameterAt(signature, restIndex)) ? ambiguous : undefined;
1662
+ // A rest the claim puts a payload or handler in is that part's.
1663
+ if (inRest.length > 0)
1664
+ return undefined;
1665
+ return competes(this.keyParameterAt(signature, restIndex)) ? ambiguous : undefined;
1666
+ }
1667
+ /**
1668
+ * A slot beside the name takes a string, read as a name there is read: a
1669
+ * conditional through its branches (`throughConditional`). One whose
1670
+ * branch says nothing counts no more than `any` does.
1671
+ */
1672
+ siblingTakesString(slot) {
1673
+ return this.acceptsString(this.throughConditional(slot));
1674
+ }
1675
+ /**
1676
+ * The string-accepting keys of an argument: every key some object part of
1677
+ * it declares, read through a type parameter's constraint.
1678
+ */
1679
+ stringKeys(slot) {
1680
+ if (slot === undefined || slot === VARIADIC)
1681
+ return [];
1682
+ const keys = new Set();
1683
+ for (const part of this.parts(this.throughConstraint(slot))) {
1684
+ if (!this.isObjectLike(part))
1685
+ continue;
1686
+ for (const property of this.namedProperties(part)) {
1687
+ if (this.siblingTakesString(this.checker.getTypeOfSymbol(property)))
1688
+ keys.add(property.getName());
1689
+ }
1690
+ }
1691
+ return [...keys];
1692
+ }
1693
+ /**
1694
+ * The parameter at `index` is declared as a key of a map with an index
1695
+ * signature: `keyof M`, or a type parameter constrained by one, where `M`
1696
+ * (or its constraint) declares `[key: string]: ...`. Such a slot takes any
1697
+ * key the map could hold, so it says nothing about a name. Read at the
1698
+ * declaration, where the map is still the type parameter the library wrote.
1699
+ */
1700
+ isIndexKeySlot(signature, index) {
1701
+ const declaration = signature.getDeclaration();
1702
+ if (!declaration)
1703
+ return false;
1704
+ const parameters = declaration.parameters;
1705
+ const parameter = parameters[Math.min(index, parameters.length - 1)];
1706
+ if (!parameter?.type)
1707
+ return false;
1708
+ if (index >= parameters.length && !parameter.dotDotDotToken)
1709
+ return false;
1710
+ return this.keysOfIndexMap(this.checker.getTypeFromTypeNode(parameter.type), parameter.type, 0);
1711
+ }
1712
+ keysOfIndexMap(type, node, depth) {
1713
+ if (depth > 6)
1714
+ return false;
1715
+ // `keyof M` written against a concrete map is resolved at once to the
1716
+ // map's keys (`string | number` for an index signature); the node keeps
1717
+ // what was written.
1718
+ if (node && ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.KeyOfKeyword) {
1719
+ return this.isIndexMap(this.checker.getTypeFromTypeNode(node.type));
1720
+ }
1721
+ if (type.flags & ts.TypeFlags.Index) {
1722
+ return this.isIndexMap(type.type);
1723
+ }
1724
+ if (type.flags & ts.TypeFlags.TypeParameter) {
1725
+ const constraintNode = this.declaredConstraint(type);
1726
+ return (constraintNode !== undefined &&
1727
+ this.keysOfIndexMap(this.checker.getTypeFromTypeNode(constraintNode), constraintNode, depth + 1));
1728
+ }
1729
+ if (type.isUnionOrIntersection()) {
1730
+ return type.types.some(part => this.keysOfIndexMap(part, undefined, depth + 1));
1731
+ }
1732
+ return false;
1733
+ }
1734
+ /** The constraint a type parameter's declaration writes, unresolved (`K extends keyof M`). */
1735
+ declaredConstraint(type) {
1736
+ return (type.getSymbol()?.declarations ?? []).find(ts.isTypeParameterDeclaration)?.constraint;
1737
+ }
1738
+ /**
1739
+ * The map a `keyof` reads is a concrete map with an index signature. A map
1740
+ * the library takes as a type parameter (an event map defaulting to an
1741
+ * index signature) is the service's to type: its own type argument only
1742
+ * narrows which names the slot allows and never moves the name to another
1743
+ * argument, so the key slot reads as a string slot (contract amendment 2,
1744
+ * B6, the `index_key_generic_map` reading, now the only one).
1745
+ */
1746
+ isIndexMap(map) {
1747
+ if (map.flags & ts.TypeFlags.TypeParameter)
1748
+ return false;
1749
+ return this.hasIndexSignature(map);
1750
+ }
1751
+ hasIndexSignature(type) {
1752
+ const target = type.flags & ts.TypeFlags.TypeParameter ? this.checker.getBaseConstraintOfType(type) ?? type : type;
1753
+ return this.checker.getIndexInfosOfType(this.checker.getApparentType(target)).length > 0;
1754
+ }
1755
+ /**
1756
+ * A handler or acknowledgement slot: every part besides null and undefined
1757
+ * is a function type with a declared signature. `Function`, `any`,
1758
+ * `unknown`, an unconstrained type parameter and `(...args: any[])` say
1759
+ * nothing about a handler (`handler_untyped`); a part that is not a
1760
+ * function (an options object, a string) is `handler_not_function`.
1761
+ */
1762
+ handlerFailure(declared) {
1763
+ if (declared === undefined)
1764
+ return failed('param_missing');
1765
+ // A listener typed by deferred conditional machinery (`ListenerOf<Ev>`)
1766
+ // is read through its branches; one that says nothing is untyped.
1767
+ const slot = this.throughConditional(this.throughConstraint(declared));
1768
+ if (slot === VARIADIC)
1769
+ return unchecked('handler_untyped');
1770
+ const parts = this.parts(slot);
1771
+ if (parts.length === 0)
1772
+ return failed('handler_not_function');
1773
+ for (const part of parts) {
1774
+ if (this.isOpenTop(part) || this.isUnconstrained(part))
1775
+ return unchecked('handler_untyped');
1776
+ const signatures = part.getCallSignatures();
1777
+ if (signatures.length === 0) {
1778
+ return this.isEmptyObject(part) ? unchecked('handler_untyped') : failed('handler_not_function');
1779
+ }
1780
+ if (signatures.every(signature => this.isUntypedSignature(signature)))
1781
+ return unchecked('handler_untyped');
1782
+ }
1783
+ return undefined;
1784
+ }
1785
+ /**
1786
+ * A name typed by a conditional (`IdOf<T> = T extends Def<infer Id> ? Id
1787
+ * : never`) is read through what its branches allow. When a branch says
1788
+ * nothing (`... ? Id : any`), so does the slot: the checker's constraint of
1789
+ * such a conditional drops the `any` branch and would read as a string.
1790
+ */
1791
+ throughConditional(slot) {
1792
+ if (slot === VARIADIC)
1793
+ return slot;
1794
+ const parts = this.parts(slot);
1795
+ if (parts.length !== 1 || !(parts[0].flags & ts.TypeFlags.Conditional))
1796
+ return slot;
1797
+ const node = parts[0].root.node;
1798
+ const branchSaysNothing = [node.trueType, node.falseType].some(branch => {
1799
+ const type = this.checker.getTypeFromTypeNode(branch);
1800
+ return (type.flags & ts.TypeFlags.Never) === 0 && this.returnSaysNothing(type);
1801
+ });
1802
+ if (branchSaysNothing)
1803
+ return VARIADIC;
1804
+ return this.checker.getBaseConstraintOfType(parts[0]) ?? slot;
1805
+ }
1806
+ /** Every part besides null and undefined is callable: a function, not data. */
1807
+ isFunctionSlot(declared) {
1808
+ const slot = this.throughConstraint(declared);
1809
+ if (slot === VARIADIC)
1810
+ return false;
1811
+ const parts = this.parts(slot);
1812
+ return parts.length > 0 && parts.every(part => !this.isOpenTop(part) && part.getCallSignatures().length > 0);
1813
+ }
1814
+ /** `(...args: any[])` or `(...args: unknown[])`: a signature that declares nothing. */
1815
+ isUntypedSignature(signature) {
1816
+ const parameters = signature.getParameters();
1817
+ if (parameters.length !== 1)
1818
+ return false;
1819
+ const declaration = parameters[0].valueDeclaration;
1820
+ if (!declaration || !ts.isParameter(declaration) || !declaration.dotDotDotToken)
1821
+ return false;
1822
+ const rest = this.checker.getTypeOfSymbol(parameters[0]);
1823
+ if (this.isOpenTop(rest))
1824
+ return true;
1825
+ return this.checker.isArrayType(rest) && this.isOpenTop(this.checker.getTypeArguments(rest)[0]);
1826
+ }
1827
+ /**
1828
+ * The string literals a slot spells: the literal parts of its type, through
1829
+ * a type parameter's constraint, and the property names of a map a `keyof`
1830
+ * reads, both as instantiated and as written at the declaration.
1831
+ */
1832
+ literalsAt(signature, at) {
1833
+ const literals = new Set();
1834
+ let slot = this.parameterAt(signature, at.arg);
1835
+ if (slot !== undefined && slot !== VARIADIC && at.key !== undefined) {
1836
+ const property = this.declaredProperty(slot, at.key);
1837
+ slot = property ? this.checker.getTypeOfSymbol(property) : undefined;
1838
+ }
1839
+ if (slot !== undefined && slot !== VARIADIC)
1840
+ this.spell(slot, literals, 0);
1841
+ if (at.key === undefined) {
1842
+ const parameters = signature.getDeclaration()?.parameters;
1843
+ const node = parameters?.[Math.min(at.arg, parameters.length - 1)]?.type;
1844
+ if (node)
1845
+ this.spell(this.checker.getTypeFromTypeNode(node), literals, 0);
1846
+ }
1847
+ return literals;
1848
+ }
1849
+ spell(type, into, depth) {
1850
+ if (depth > 6)
1851
+ return;
1852
+ if (type.isStringLiteral()) {
1853
+ into.add(type.value);
1854
+ return;
1855
+ }
1856
+ if (type.isUnionOrIntersection()) {
1857
+ for (const part of type.types)
1858
+ this.spell(part, into, depth + 1);
1859
+ return;
1860
+ }
1861
+ if (type.flags & ts.TypeFlags.TypeParameter) {
1862
+ const constraintNode = this.declaredConstraint(type);
1863
+ this.spell(constraintNode ? this.checker.getTypeFromTypeNode(constraintNode) : this.checker.getBaseConstraintOfType(type) ?? type, into, depth + 1);
1864
+ return;
1865
+ }
1866
+ if (type.flags & ts.TypeFlags.Index) {
1867
+ let map = type.type;
1868
+ if (map.flags & ts.TypeFlags.TypeParameter) {
1869
+ map = this.checker.getDefaultFromTypeParameter(map) ?? this.checker.getBaseConstraintOfType(map) ?? map;
1870
+ }
1871
+ for (const property of this.checker.getPropertiesOfType(this.checker.getApparentType(map))) {
1872
+ if (!isSymbolKeyed(property))
1873
+ into.add(property.getName());
1874
+ }
1875
+ }
1876
+ }
1877
+ /**
1878
+ * The callee of a message claim: the claim's member `path` walked from the
1879
+ * receiver hop by hop (`client.tasks.trigger`), then `member` of the object
1880
+ * reached (or that object itself, when null), called (`call`) or
1881
+ * constructed (`new`), through the signatures its home packages declare.
1882
+ * A member, or every signature, that only another package declares (the
1883
+ * runtime's event emitter a library class extends, a base class from a
1884
+ * dependency) is `member_inherited`. Each hop must be a home member too;
1885
+ * the object it reaches adds the packages that declare its type to the
1886
+ * home, never the runtime's, and a hop whose type says nothing has no
1887
+ * members to read (`member_untyped`).
1888
+ */
1889
+ ownCallee(receiver, hops, member, form) {
1890
+ let holder = receiver;
1891
+ for (const hop of hops) {
1892
+ const step = this.ownMember(holder, hop);
1893
+ if ('failure' in step)
1894
+ return step;
1895
+ const type = step.type;
1896
+ if (this.saysNothing(type))
1897
+ return { failure: unchecked('member_untyped') };
1898
+ holder = { type, home: this.homeOf(receiver.pkg, typeSymbols(type), holder.home) };
1899
+ }
1900
+ let type = holder.type;
1901
+ // A member of a base the holder binds with its own type (see `isBoundByOwnType`).
1902
+ let boundBase = false;
1903
+ if (member !== null) {
1904
+ const step = this.ownMember(holder, member);
1905
+ if ('failure' in step)
1906
+ return step;
1907
+ type = step.type;
1908
+ boundBase = step.bound;
1909
+ }
1910
+ const all = form === 'new'
1911
+ ? this.checker.getNonNullableType(type).getConstructSignatures()
1912
+ : this.checker.getNonNullableType(type).getCallSignatures();
1913
+ const library = all.filter(signature => {
1914
+ const file = this.signatureFile(signature, type, form);
1915
+ return file !== undefined && this.isLibraryFile(file);
1916
+ });
1917
+ const home = holder.home;
1918
+ const own = boundBase
1919
+ ? library
1920
+ : library.filter(signature => this.isOwnFile(this.signatureFile(signature, type, form), home));
1921
+ if (own.length > 0)
1922
+ return { signatures: own };
1923
+ if (library.length > 0)
1924
+ return { failure: failed('member_inherited') };
1925
+ return { failure: this.slotFailure(type, form === 'new' ? 'member_not_constructible' : 'member_not_callable') };
1926
+ }
1927
+ /**
1928
+ * Member `name` of `holder`, when its home packages declare it, or when it
1929
+ * sits on another package's base that the holder binds with its own type
1930
+ * (`bound`).
1931
+ */
1932
+ ownMember(holder, name) {
1933
+ const apparent = this.checker.getApparentType(this.checker.getNonNullableType(holder.type));
1934
+ const property = this.declaredProperty(holder.type, name);
1935
+ if (!property)
1936
+ return { failure: failed('member_missing') };
1937
+ const type = this.checker.getTypeOfSymbol(property);
1938
+ if (this.isOwnMember(property, apparent, holder.home))
1939
+ return { type, bound: false };
1940
+ if (this.isBoundByOwnType(property, holder))
1941
+ return { type, bound: true };
1942
+ return { failure: failed('member_inherited') };
1943
+ }
1944
+ /**
1945
+ * An inherited emitter the client binds to its own interface counts as
1946
+ * declared: the member is declared on a base type another package writes,
1947
+ * and the holder's class binds that base with at least one concrete type
1948
+ * argument (not a type parameter) its home packages declare:
1949
+ * `class Socket<L, E> extends Emitter<L, E, SocketReservedEvents>`. The
1950
+ * runtime's emitter, extended with no type of the package's own, is not
1951
+ * bound; nor is a base bound only through the class's type parameters.
1952
+ */
1953
+ isBoundByOwnType(property, holder) {
1954
+ const owners = (property.declarations ?? [])
1955
+ .map(declaration => declaration.parent)
1956
+ .filter((owner) => owner !== undefined && (ts.isClassLike(owner) || ts.isInterfaceDeclaration(owner)));
1957
+ if (owners.length === 0)
1958
+ return false;
1959
+ const targetOf = (type) => (type.objectFlags ?? 0) & ts.ObjectFlags.Reference ? type.target : type;
1960
+ const isOwnType = (type) => !(type.flags & ts.TypeFlags.TypeParameter) &&
1961
+ typeSymbols(type).some(symbol => (symbol?.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), holder.home)));
1962
+ const visit = (type, depth) => {
1963
+ if (depth > 8)
1964
+ return false;
1965
+ if (type.isIntersection())
1966
+ return type.types.some(part => visit(part, depth + 1));
1967
+ const target = targetOf(type);
1968
+ if (!((target.objectFlags ?? 0) & ts.ObjectFlags.ClassOrInterface))
1969
+ return false;
1970
+ for (const base of this.checker.getBaseTypes(target)) {
1971
+ const declarations = targetOf(base).getSymbol()?.declarations ?? [];
1972
+ if (owners.some(owner => declarations.includes(owner))) {
1973
+ const bound = (base.objectFlags ?? 0) & ts.ObjectFlags.Reference
1974
+ ? this.checker.getTypeArguments(base)
1975
+ : [];
1976
+ if (bound.some(isOwnType))
1977
+ return true;
1978
+ continue;
1979
+ }
1980
+ if (visit(base, depth + 1))
1981
+ return true;
1982
+ }
1983
+ return false;
1984
+ };
1985
+ return visit(this.checker.getNonNullableType(holder.type), 0);
1986
+ }
1987
+ /**
1988
+ * The file that wrote a signature. A class's construct signatures are the
1989
+ * class's own, wherever the constructor they reuse was written: a class
1990
+ * with no constructor of its own is built through its base's (or a default
1991
+ * one with no declaration at all), and still builds an instance of itself.
1992
+ */
1993
+ signatureFile(signature, callee, form) {
1994
+ if (form === 'new') {
1995
+ const declaration = callee.getSymbol()?.declarations?.find(ts.isClassLike);
1996
+ if (declaration)
1997
+ return declaration.getSourceFile();
1998
+ }
1999
+ return signature.getDeclaration()?.getSourceFile();
2000
+ }
2001
+ /**
2002
+ * Some declaration of the member is in one of the receiver's own packages.
2003
+ * A member a mapped type makes has no declaration of its own; it is own when
2004
+ * it reaches the receiver along types the own packages (or the default
2005
+ * library's utility types, `Record`) declare, and the service's own blocks
2006
+ * do not name it.
2007
+ */
2008
+ isOwnMember(property, listing, home) {
2009
+ const declarations = property.declarations ?? [];
2010
+ if (declarations.length === 0) {
2011
+ return (!this.isNamedByServiceAugmentation(property.getName()) &&
2012
+ this.isListedByLibraryType(listing, property.getName(), file => this.program.isSourceFileDefaultLibrary(file) || this.isOwnFile(file, home)));
2013
+ }
2014
+ return declarations.some(declaration => this.isOwnFile(declaration.getSourceFile(), home));
2015
+ }
2016
+ // --------------------------------------------------------------------------
2017
+ // Surface listing (carrick#1660)
2018
+ // --------------------------------------------------------------------------
2019
+ /**
2020
+ * One specifier's declared surface (see `LibraryClaimsVerifier.listSurface`).
2021
+ * `built` answers the probe's no-argument build of a receiver an export
2022
+ * makes, resolved, when the probe holds one.
2023
+ */
2024
+ listPackage(pkg, probe, maxEntries, only, built) {
2025
+ this.setPackage(pkg);
2026
+ // Runtime modules on: a `node:` specifier is listed from the runtime's
2027
+ // types package, as the message roles read it.
2028
+ const moduleRead = this.readModule(pkg, probe.namespace, true);
2029
+ const surface = { package: pkg, truncated: 0, exports: [] };
2030
+ if (moduleRead.entry.resolved_file)
2031
+ surface.resolved_file = moduleRead.entry.resolved_file;
2032
+ if (moduleRead.entry.installed_version)
2033
+ surface.installed_version = moduleRead.entry.installed_version;
2034
+ if (moduleRead.reason)
2035
+ return { ...surface, reason: moduleRead.reason };
2036
+ const exported = this.listedExports(probe, only);
2037
+ if (!exported)
2038
+ return { ...surface, reason: 'module_unresolved' };
2039
+ let budget = maxEntries;
2040
+ let dropped = 0;
2041
+ const take = () => {
2042
+ if (budget > 0) {
2043
+ budget -= 1;
2044
+ return true;
2045
+ }
2046
+ dropped += 1;
2047
+ return false;
2048
+ };
2049
+ // Three passes: every export and receiver, then every member name, then signatures.
2050
+ const names = [];
2051
+ const fills = [];
2052
+ for (const { name, target, type } of exported) {
2053
+ if (!take())
2054
+ continue;
2055
+ const entry = { export: name, receivers: [] };
2056
+ surface.exports.push(entry);
2057
+ if (this.isOpenTop(type))
2058
+ continue;
2059
+ const receiverOf = (receiverName, receiverType) => {
2060
+ if (!take())
2061
+ return;
2062
+ const home = this.homeOf(pkg, [target, ...typeSymbols(receiverType)]);
2063
+ entry.receivers.push(this.outlineReceiver(pkg, receiverName, receiverType, home, take, names, fills));
2064
+ };
2065
+ receiverOf('export', type);
2066
+ for (const maker of this.makers(pkg, target, type)) {
2067
+ const made = this.madeBy(maker.signatures, built(name, maker.receiver));
2068
+ if (!made)
2069
+ continue;
2070
+ // One level below the export, what the member builds must declare a callable member.
2071
+ if (maker.member &&
2072
+ !this.namedProperties(made).some(member => this.librarySignatures(this.checker.getTypeOfSymbol(member), 'call').length > 0)) {
2073
+ continue;
2074
+ }
2075
+ receiverOf(maker.receiver, made);
2076
+ }
2077
+ }
2078
+ for (const run of names)
2079
+ run();
2080
+ for (const fill of fills)
2081
+ fill();
2082
+ surface.truncated = dropped;
2083
+ return surface;
2084
+ }
2085
+ /**
2086
+ * The receivers each export of a specifier can make, by receiver id, for
2087
+ * the probe statements that build them (see `LibraryClaimsVerifier.listSurface`).
2088
+ */
2089
+ makersOf(pkg, probe, only) {
2090
+ this.setPackage(pkg);
2091
+ if (this.readModule(pkg, probe.namespace, true).reason)
2092
+ return [];
2093
+ return (this.listedExports(probe, only) ?? [])
2094
+ .filter(({ type }) => !this.isOpenTop(type))
2095
+ .map(({ name, target, type }) => ({
2096
+ export: name,
2097
+ receivers: this.makers(pkg, target, type).map(maker => maker.receiver),
2098
+ }))
2099
+ .filter(({ receivers }) => receivers.length > 0);
2100
+ }
2101
+ /**
2102
+ * The value exports a specifier lists (every one, or only those `only`
2103
+ * names), sorted by name: what its namespace holds, and `default` for a
2104
+ * module that exports a value whole (`export =`), as a default import gets
2105
+ * it, when that import reads a typed value (`readExport`, as the verifier
2106
+ * reads `default`). Such a module's namespace holds its statics, and its
2107
+ * `prototype`, which no service imports. Undefined when the specifier
2108
+ * resolves to no module symbol.
2109
+ */
2110
+ listedExports(probe, only) {
2111
+ const moduleSymbol = this.checker.getSymbolAtLocation(probe.namespace.moduleSpecifier);
2112
+ if (!moduleSymbol)
2113
+ return undefined;
2114
+ const wanted = (name) => only === undefined || only.includes(name);
2115
+ const all = this.checker.getExportsOfModule(moduleSymbol);
2116
+ const listed = [];
2117
+ for (const symbol of all) {
2118
+ const target = symbol.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(symbol) : symbol;
2119
+ if (this.checker.isUnknownSymbol(target) ||
2120
+ (target.flags & ts.SymbolFlags.Value) === 0 ||
2121
+ (target.flags & ts.SymbolFlags.Prototype) !== 0 ||
2122
+ !wanted(symbol.getName())) {
2123
+ continue;
2124
+ }
2125
+ listed.push({ name: symbol.getName(), target, type: this.checker.getTypeOfSymbolAtLocation(symbol, probe.namespace) });
2126
+ }
2127
+ if (moduleSymbol.exports?.has(ts.InternalSymbolName.ExportEquals) &&
2128
+ !all.some(symbol => symbol.getName() === 'default') &&
2129
+ wanted('default')) {
2130
+ const read = this.readExport(probe.defaultImport);
2131
+ if (!('reason' in read))
2132
+ listed.push({ name: 'default', target: read.value.target, type: read.value.type });
2133
+ }
2134
+ return listed.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
2135
+ }
2136
+ /**
2137
+ * The ways an export makes a receiver, in listing order: calling it
2138
+ * (`instance:()`), constructing it (`instance:new`), then calling or
2139
+ * constructing each member (`instance:<member>`, `instance:new:<member>`),
2140
+ * each through the signatures a maker claim reads there (`ownCallee`): its
2141
+ * home's own, or a base's it binds with its own type. A maker the export
2142
+ * only inherits from another package is none the verifier reads
2143
+ * (`member_inherited`).
2144
+ */
2145
+ makers(pkg, target, type) {
2146
+ const holder = this.surfaceHolder(pkg, type, this.homeOf(pkg, [target, ...typeSymbols(type)]));
2147
+ const makers = [];
2148
+ for (const [form, receiver] of [['call', 'instance:()'], ['new', 'instance:new']]) {
2149
+ const callee = this.ownCallee(holder, [], null, form);
2150
+ if ('signatures' in callee)
2151
+ makers.push({ receiver, member: false, signatures: callee.signatures });
2152
+ }
2153
+ for (const property of sortedByName(this.namedProperties(type))) {
2154
+ for (const [form, prefix] of [['call', 'instance:'], ['new', 'instance:new:']]) {
2155
+ const callee = this.ownCallee(holder, [], property.getName(), form);
2156
+ if ('signatures' in callee) {
2157
+ makers.push({ receiver: `${prefix}${property.getName()}`, member: true, signatures: callee.signatures });
2158
+ }
2159
+ }
2160
+ }
2161
+ return makers;
2162
+ }
2163
+ /** A receiver read for the listing as a message claim reads it (`ownCallee`). */
2164
+ surfaceHolder(pkg, type, home) {
2165
+ return { type, pkg, home, makerBindsName: false, scoped: false };
2166
+ }
2167
+ /**
2168
+ * What a maker builds: each overload's return, read at its declared
2169
+ * type-parameter defaults where `built` (the probe's no-argument build of
2170
+ * the receiver) instantiates it (`returnOf`), less the returns that say
2171
+ * nothing, which no maker claim holds on. Listed when what is left is one
2172
+ * object type.
2173
+ */
2174
+ madeBy(signatures, built) {
2175
+ const returns = [
2176
+ ...new Set(signatures.map(sig => this.returnOf(sig, built)).filter(type => !this.returnSaysNothing(type))),
2177
+ ];
2178
+ return returns.length === 1 && this.isObjectType(returns[0]) ? returns[0] : undefined;
2179
+ }
2180
+ /** The call (or construct) signatures of `type` an installed package or the default library declares. */
2181
+ librarySignatures(type, form) {
2182
+ const nonNullable = this.checker.getNonNullableType(type);
2183
+ const all = form === 'new' ? nonNullable.getConstructSignatures() : nonNullable.getCallSignatures();
2184
+ return all.filter(signature => {
2185
+ const file = this.signatureFile(signature, nonNullable, form);
2186
+ return file !== undefined && this.isLibraryFile(file);
2187
+ });
2188
+ }
2189
+ /**
2190
+ * A receiver, now; its member names later (`names`), once every export and
2191
+ * receiver of the package is listed; its call, construct and member
2192
+ * signatures last (`fills`), once every name is.
2193
+ */
2194
+ outlineReceiver(pkg, receiverName, type, home, take, names, fills) {
2195
+ const receiver = { receiver: receiverName, members: [] };
2196
+ const holder = this.surfaceHolder(pkg, type, home);
2197
+ // The receiver's own call and construct signatures, as a claim with a
2198
+ // null member reads them.
2199
+ const call = this.ownCallee(holder, [], null, 'call');
2200
+ if ('signatures' in call)
2201
+ fills.push(() => (receiver.call = this.listSignatures(call.signatures, take)));
2202
+ const construct = this.ownCallee(holder, [], null, 'new');
2203
+ if ('signatures' in construct) {
2204
+ fills.push(() => (receiver.construct = this.listSignatures(construct.signatures, take)));
2205
+ }
2206
+ if (this.isOpenTop(type))
2207
+ return receiver;
2208
+ names.push(() => {
2209
+ for (const property of sortedByName(this.namedProperties(type))) {
2210
+ const library = this.librarySignatures(this.checker.getTypeOfSymbol(property), 'call');
2211
+ if (library.length === 0 || !take())
2212
+ continue;
2213
+ // Own when an op claim reads the member at all (`ownCallee`): its
2214
+ // home declares it, or it sits on a base the receiver binds with its
2215
+ // own type, and its home writes a signature of it. An inherited
2216
+ // member is listed with every library signature.
2217
+ const callee = this.ownCallee(holder, [], property.getName(), 'call');
2218
+ const own = 'signatures' in callee;
2219
+ const member = { name: property.getName(), own, signatures: [] };
2220
+ receiver.members.push(member);
2221
+ fills.push(() => (member.signatures = this.listSignatures(own ? callee.signatures : library, take)));
2222
+ }
2223
+ });
2224
+ return receiver;
2225
+ }
2226
+ listSignatures(signatures, take) {
2227
+ const listed = [];
2228
+ for (const signature of signatures) {
2229
+ if (!take())
2230
+ continue;
2231
+ const params = [];
2232
+ const parameters = signature.getParameters();
2233
+ for (let index = 0; index < parameters.length; index++) {
2234
+ if (!take())
2235
+ continue;
2236
+ const parameter = parameters[index];
2237
+ const declaration = parameter.valueDeclaration;
2238
+ const isParameter = declaration !== undefined && ts.isParameter(declaration);
2239
+ // What a claim part at this argument is checked against: a rest's
2240
+ // element, a type parameter's constraint (`keyParameterAt`).
2241
+ const slot = this.keyParameterAt(signature, index);
2242
+ const param = {
2243
+ name: parameter.getName(),
2244
+ optional: isParameter && (declaration.questionToken !== undefined || declaration.initializer !== undefined),
2245
+ rest: isParameter && declaration.dotDotDotToken !== undefined,
2246
+ type: this.printed(this.checker.getTypeOfSymbol(parameter)),
2247
+ // The checks a positional name and a positional handler pass there.
2248
+ accepts_string: slot !== undefined && this.acceptsString(this.throughConditional(slot)),
2249
+ function: this.handlerFailure(slot) === undefined,
2250
+ };
2251
+ if (slot !== undefined && slot !== VARIADIC && this.isObjectType(slot) && this.handlerFailure(slot) !== undefined) {
2252
+ const keys = this.listKeys(slot, take);
2253
+ if (keys.length > 0)
2254
+ param.keys = keys;
2255
+ }
2256
+ const literals = this.literalsAt(signature, { arg: index });
2257
+ if (literals.size > 0)
2258
+ param.literals = [...literals].sort();
2259
+ params.push(param);
2260
+ }
2261
+ listed.push({ params, returns: this.printed(this.checker.getReturnTypeOfSignature(signature)) });
2262
+ }
2263
+ return listed;
2264
+ }
2265
+ /**
2266
+ * The keys a claim can name at an object argument, by name: every key of
2267
+ * each view the verifier reads one at a time (`keyViews`). A
2268
+ * key accepts a string, or is a handler, when it does so in some view, as
2269
+ * a keyed name or handler is checked (`viewFor`). It is optional when a
2270
+ * call can leave it out: some view marks it optional or does not declare it.
2271
+ */
2272
+ listKeys(slot, take) {
2273
+ const views = this.keyViews(slot);
2274
+ const keys = new Map();
2275
+ for (const view of views) {
2276
+ for (const property of this.namedProperties(view)) {
2277
+ const type = this.checker.getTypeOfSymbol(property);
2278
+ const read = {
2279
+ name: property.getName(),
2280
+ optional: (property.flags & ts.SymbolFlags.Optional) !== 0,
2281
+ accepts_string: this.acceptsString(this.throughConditional(type)),
2282
+ function: this.handlerFailure(type) === undefined,
2283
+ views: 1,
2284
+ };
2285
+ const seen = keys.get(read.name);
2286
+ if (!seen) {
2287
+ keys.set(read.name, read);
2288
+ continue;
2289
+ }
2290
+ seen.optional = seen.optional || read.optional;
2291
+ seen.accepts_string = seen.accepts_string || read.accepts_string;
2292
+ seen.function = seen.function || read.function;
2293
+ seen.views += 1;
2294
+ }
2295
+ }
2296
+ return [...keys.values()]
2297
+ .sort((a, b) => byName(a.name, b.name))
2298
+ .filter(() => take())
2299
+ .map(({ views: declaredIn, ...key }) => ({ ...key, optional: key.optional || declaredIn < views.length }));
2300
+ }
2301
+ /**
2302
+ * A type as the declarations print it, with the service root written as
2303
+ * `<root>` (the checker spells a type no entry exports through the file that
2304
+ * declares it), cut to a length a listing can carry. The root goes first,
2305
+ * so where the cut falls does not depend on where the package is installed.
2306
+ */
2307
+ printed(type) {
2308
+ const node = this.checker.typeToTypeNode(type, undefined, PRINT_FLAGS);
2309
+ const text = node === undefined ? this.checker.typeToString(type) : printTypeNode(this.orderUnions(node));
2310
+ return truncate(this.scrubbed(text));
2311
+ }
2312
+ /** `text` with the service root written as `<root>`. */
2313
+ scrubbed(text) {
2314
+ for (const root of this.printedRoots)
2315
+ text = text.split(root).join('<root>');
2316
+ return text;
2317
+ }
2318
+ /**
2319
+ * `node` with the members of every union in it, at every depth (inside a
2320
+ * function or a conditional type too), sorted by their own print with the
2321
+ * root scrubbed, compared by UTF-16 code unit. The compiler prints a union
2322
+ * in the order it made the members, which moves with whatever the program
2323
+ * read first.
2324
+ */
2325
+ orderUnions(node) {
2326
+ const result = ts.transform(node, [
2327
+ context => root => {
2328
+ const visit = (child) => {
2329
+ const next = ts.visitEachChild(child, visit, context);
2330
+ if (!ts.isUnionTypeNode(next))
2331
+ return next;
2332
+ const keyed = next.types.map(member => ({ member, key: this.scrubbed(printTypeNode(member)) }));
2333
+ keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
2334
+ return ts.factory.updateUnionTypeNode(next, ts.factory.createNodeArray(keyed.map(({ member }) => member)));
2335
+ };
2336
+ return ts.visitNode(root, visit);
2337
+ },
2338
+ ]);
2339
+ const [ordered] = result.transformed;
2340
+ result.dispose();
2341
+ return ordered;
2342
+ }
2343
+ // --------------------------------------------------------------------------
2344
+ // Definitions
2345
+ // --------------------------------------------------------------------------
2346
+ /**
2347
+ * A declared property `name` of `type` whose type has a call signature. A
2348
+ * member typed `any`, `unknown` or `{}` says nothing (`member_untyped`).
2349
+ */
2350
+ callableProperty(type, name) {
2351
+ const property = this.declaredProperty(type, name);
2352
+ if (!property)
2353
+ return { failure: failed('member_missing') };
2354
+ return this.callSignatures(this.checker.getTypeOfSymbol(property));
2355
+ }
2356
+ /**
2357
+ * The call signatures the library declares. A service `declare module`
2358
+ * block can add an overload to a library member; that signature is the
2359
+ * service's claim about the library, not the library's.
2360
+ */
2361
+ callSignatures(type) {
2362
+ const signatures = this.checker
2363
+ .getNonNullableType(type)
2364
+ .getCallSignatures()
2365
+ .filter(signature => {
2366
+ const declaration = signature.getDeclaration();
2367
+ return declaration !== undefined && this.isLibraryFile(declaration.getSourceFile());
2368
+ });
2369
+ if (signatures.length > 0)
2370
+ return { signatures };
2371
+ return { failure: this.slotFailure(type, 'member_not_callable') };
2372
+ }
2373
+ /**
2374
+ * A declared property of `type`: one the checker lists on its apparent type
2375
+ * with null and undefined removed, and not one every value of that kind
2376
+ * inherits (`constructor`, `toString`, a primitive wrapper's members). An
2377
+ * index signature does not count. Nor does a property typed `never` (or
2378
+ * only `undefined`): it cannot be passed. And a library declares it: at
2379
+ * least one declaration sits in an installed package or the default
2380
+ * library, so a member only the service's own module augmentation adds is
2381
+ * not the package's.
2382
+ */
2383
+ declaredProperty(slot, name) {
2384
+ return this.namedProperties(slot).find(property => property.getName() === name);
2385
+ }
2386
+ /**
2387
+ * The property `name` of a parameter a key is looked for on. It is the
2388
+ * declared property of the whole type; failing that, for a union, one that an
2389
+ * object constituent declares and whose type passes `accepts`. A constituent
2390
+ * counts only when it is an object type that says something and is not a
2391
+ * function type, so defaults written as `Options | ((parent) => Options)`
2392
+ * are read through `Options`. When a constituent declares the name but no
2393
+ * declaration passes `accepts`, that property is returned for the caller to
2394
+ * reject.
2395
+ */
2396
+ keyProperty(slot, name, accepts) {
2397
+ const whole = this.declaredProperty(slot, name);
2398
+ if (whole || slot === VARIADIC)
2399
+ return whole;
2400
+ let declaredOnly;
2401
+ for (const part of this.parts(slot)) {
2402
+ if (!this.isPlainObject(part))
2403
+ continue;
2404
+ const property = this.declaredProperty(part, name);
2405
+ if (!property)
2406
+ continue;
2407
+ if (accepts(this.checker.getTypeOfSymbol(property)))
2408
+ return property;
2409
+ declaredOnly ??= property;
2410
+ }
2411
+ return declaredOnly;
2412
+ }
2413
+ /**
2414
+ * An object type that is not a function type. (One that says nothing
2415
+ * declares no property, so it never supplies a key.)
2416
+ */
2417
+ isPlainObject(type) {
2418
+ return (this.isObjectLike(type) &&
2419
+ type.getCallSignatures().length === 0 &&
2420
+ type.getConstructSignatures().length === 0);
2421
+ }
2422
+ /**
2423
+ * A parameter a key is looked for on, or that must accept a string: read
2424
+ * through a rest parameter's element and through a type parameter's
2425
+ * constraint. A type parameter with no constraint, or one of `any` or
2426
+ * `unknown`, stays as it is and says nothing.
2427
+ */
2428
+ keyParameterAt(signature, index) {
2429
+ const slot = this.parameterAt(signature, index, true);
2430
+ return slot === undefined ? undefined : this.throughConstraint(slot);
2431
+ }
2432
+ throughConstraint(slot) {
2433
+ if (slot === VARIADIC)
2434
+ return slot;
2435
+ const parts = this.parts(slot);
2436
+ if (parts.length !== 1 || !(parts[0].flags & ts.TypeFlags.TypeParameter))
2437
+ return slot;
2438
+ // An `any` or `unknown` constraint says nothing, as the bare parameter does.
2439
+ return this.checker.getBaseConstraintOfType(parts[0]) ?? slot;
2440
+ }
2441
+ /**
2442
+ * The declared properties a claim can name: every one keyed by a string. A
2443
+ * member or key keyed by a unique symbol (`[Symbol.iterator]`, the runtime
2444
+ * emitter's `[captureRejectionSymbol]`) has no name a claim can carry, and
2445
+ * the checker names it with a symbol id that moves whenever anything
2446
+ * earlier in the process does (`__@iterator@84`): it is never listed, and
2447
+ * it neither satisfies nor blocks a slot or a name (D2).
2448
+ */
2449
+ namedProperties(slot) {
2450
+ return this.declaredProperties(slot).filter(property => !isSymbolKeyed(property));
2451
+ }
2452
+ declaredProperties(slot) {
2453
+ if (slot === VARIADIC)
2454
+ return [];
2455
+ const cached = this.declared.get(slot);
2456
+ if (cached)
2457
+ return cached;
2458
+ const apparent = this.checker.getApparentType(this.checker.getNonNullableType(slot));
2459
+ const declared = this.checker
2460
+ .getPropertiesOfType(apparent)
2461
+ .filter(property => !this.isBuiltinMember(property) &&
2462
+ this.isLibraryDeclared(property, apparent) &&
2463
+ !this.isAbsent(property));
2464
+ this.declared.set(slot, declared);
2465
+ return declared;
2466
+ }
2467
+ /**
2468
+ * Some declaration of the property is in an installed package or the
2469
+ * default library. A member a mapped type makes (`extends Record<'get',
2470
+ * Fn>`) has no declaration of its own; it counts when the type listing it
2471
+ * was written by the library.
2472
+ */
2473
+ isLibraryDeclared(property, listing) {
2474
+ const declarations = property.declarations ?? [];
2475
+ if (declarations.length === 0) {
2476
+ return (!this.isNamedByServiceAugmentation(property.getName()) &&
2477
+ this.isListedByLibraryType(listing, property.getName(), file => this.isLibraryFile(file)));
2478
+ }
2479
+ return declarations.some(declaration => this.isLibraryFile(declaration.getSourceFile()));
2480
+ }
2481
+ /** Judge the next check as a claim about `pkg`. */
2482
+ setPackage(pkg) {
2483
+ const named = packageNameOf(pkg);
2484
+ if (named !== this.currentPackage)
2485
+ this.declared.clear();
2486
+ this.currentPackage = named;
2487
+ }
2488
+ /**
2489
+ * The service's own `declare module '<this package>'` (or one of its
2490
+ * subpaths) or `declare global` blocks declare a member of this name,
2491
+ * compared without case. A mapped type's member has no declaration of its
2492
+ * own, so when the service adds a key to the interface a library mapped
2493
+ * type iterates (`Record<keyof MethodMap, Fn>`, with or without `& string`,
2494
+ * or re-cased by an `as Lowercase<...>` remap), nothing on the member says
2495
+ * the service put it there. Blocks for other packages do not count: a
2496
+ * service augments many packages, and their member names say nothing about
2497
+ * this one.
2498
+ */
2499
+ isNamedByServiceAugmentation(name) {
2500
+ const names = this.serviceAugmentedNames();
2501
+ const lower = name.toLowerCase();
2502
+ return Boolean(names.get(this.currentPackage)?.has(lower) || names.get('global')?.has(lower));
2503
+ }
2504
+ serviceAugmentedNames() {
2505
+ if (this.augmentedNames)
2506
+ return this.augmentedNames;
2507
+ const byPackage = new Map();
2508
+ const collect = (node, names) => {
2509
+ if ((ts.isPropertySignature(node) ||
2510
+ ts.isMethodSignature(node) ||
2511
+ ts.isPropertyDeclaration(node) ||
2512
+ ts.isMethodDeclaration(node) ||
2513
+ ts.isEnumMember(node)) &&
2514
+ (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name) || ts.isNumericLiteral(node.name))) {
2515
+ names.add(node.name.text.toLowerCase());
2516
+ }
2517
+ ts.forEachChild(node, child => collect(child, names));
2518
+ };
2519
+ for (const file of this.program.getSourceFiles()) {
2520
+ if (file === this.probe || this.isLibraryFile(file))
2521
+ continue;
2522
+ for (const statement of file.statements) {
2523
+ if (!ts.isModuleDeclaration(statement) || !statement.body)
2524
+ continue;
2525
+ const key = ts.isStringLiteral(statement.name)
2526
+ ? packageNameOf(statement.name.text)
2527
+ : statement.flags & ts.NodeFlags.GlobalAugmentation
2528
+ ? 'global'
2529
+ : undefined;
2530
+ if (key === undefined)
2531
+ continue;
2532
+ let names = byPackage.get(key);
2533
+ if (!names)
2534
+ byPackage.set(key, (names = new Set()));
2535
+ collect(statement.body, names);
2536
+ }
2537
+ }
2538
+ this.augmentedNames = byPackage;
2539
+ return byPackage;
2540
+ }
2541
+ /**
2542
+ * Member `name`, which has no declaration of its own, reaches `listing`
2543
+ * along a path of types `isAllowed` files alone declare. The path runs
2544
+ * through the types that list the member: an intersection's parts
2545
+ * (`type Client = {...} & Record<Alias, Fn> & Fn`), and an interface's base
2546
+ * types (`interface S extends Base`), each declared only in allowed files.
2547
+ * The library's `Record` is; a base interface the service extended with its
2548
+ * own mapped type is not, at any depth.
2549
+ */
2550
+ isListedByLibraryType(listing, name, isAllowed) {
2551
+ const listedBy = (types) => types.some(part => {
2552
+ const apparent = this.checker.getApparentType(part);
2553
+ return (this.checker.getPropertyOfType(apparent, name) !== undefined &&
2554
+ this.isListedByLibraryType(apparent, name, isAllowed));
2555
+ });
2556
+ if (listing.isIntersection())
2557
+ return listedBy(listing.types);
2558
+ if (!this.isDeclaredOnlyIn(listing.getSymbol(), isAllowed))
2559
+ return false;
2560
+ const objectFlags = listing.objectFlags ?? 0;
2561
+ const target = objectFlags & ts.ObjectFlags.Reference ? listing.target : listing;
2562
+ const targetFlags = target.objectFlags ?? 0;
2563
+ // A type literal or mapped type lists its members itself.
2564
+ if (!(targetFlags & ts.ObjectFlags.ClassOrInterface))
2565
+ return true;
2566
+ return listedBy(this.checker.getBaseTypes(target));
2567
+ }
2568
+ isDeclaredOnlyIn(symbol, isAllowed) {
2569
+ const declarations = symbol?.declarations ?? [];
2570
+ return declarations.length > 0 && declarations.every(declaration => isAllowed(declaration.getSourceFile()));
2571
+ }
2572
+ /** An installed package's file, or the default library's. */
2573
+ isLibraryFile(file) {
2574
+ return this.program.isSourceFileDefaultLibrary(file) || this.isInstalledFile(file.fileName);
2575
+ }
2576
+ isInstalledFile(fileName) {
2577
+ return this.installedPackage(fileName) !== undefined;
2578
+ }
2579
+ /**
2580
+ * The installed package a file belongs to. Its path, taken relative to the
2581
+ * service root, has a package directory after its last `node_modules`
2582
+ * segment (two segments for a scope), holding a package.json. A service
2583
+ * source file in a directory that happens to be named `node_modules`, or a
2584
+ * repository checked out under a `node_modules` ancestor, is not installed.
2585
+ * An install hoisted above the service root (`../../node_modules/pkg`) and
2586
+ * a pnpm store (`node_modules/.pnpm/pkg@1/node_modules/pkg`) are.
2587
+ */
2588
+ installedPackage(fileName) {
2589
+ const segments = path.relative(this.root, this.realpath(fileName)).split(path.sep);
2590
+ const last = segments.lastIndexOf('node_modules');
2591
+ if (last < 0)
2592
+ return undefined;
2593
+ const width = segments[last + 1]?.startsWith('@') ? 2 : 1;
2594
+ const nameSegments = segments.slice(last + 1, last + 1 + width);
2595
+ // No package.json there (a file directly under node_modules included) means no package.
2596
+ const directory = path.resolve(this.root, ...segments.slice(0, last + 1 + width));
2597
+ let known = this.packageDirectories.get(directory);
2598
+ if (known === undefined) {
2599
+ known = readInstalledPackage(this.host, directory, nameSegments.join('/'));
2600
+ this.packageDirectories.set(directory, known);
2601
+ }
2602
+ return known ?? undefined;
2603
+ }
2604
+ /** Typed so that nothing can be passed: `never`, or only `undefined`. */
2605
+ isAbsent(property) {
2606
+ return this.parts(this.checker.getTypeOfSymbol(property)).every(part => (part.flags & ts.TypeFlags.Never) !== 0);
2607
+ }
2608
+ isBuiltinMember(property) {
2609
+ const declarations = property.declarations ?? [];
2610
+ return (declarations.length > 0 &&
2611
+ declarations.every(declaration => {
2612
+ const owner = declaration.parent;
2613
+ if (!owner || !ts.isInterfaceDeclaration(owner))
2614
+ return false;
2615
+ const symbol = this.checker.getSymbolAtLocation(owner.name);
2616
+ return symbol !== undefined && BUILTIN_INTERFACES.has(this.checker.getFullyQualifiedName(symbol));
2617
+ }));
2618
+ }
2619
+ /**
2620
+ * `string` is assignable to the type, and some part of it besides null and
2621
+ * undefined is string-like. `any`, `unknown`, `{}` and `Object` accept a
2622
+ * string without saying anything about one.
2623
+ */
2624
+ acceptsString(declared) {
2625
+ const slot = this.throughConstraint(declared);
2626
+ if (slot === VARIADIC)
2627
+ return false;
2628
+ return (this.checker.isTypeAssignableTo(this.checker.getStringType(), slot) &&
2629
+ this.parts(slot).some(part => isStringLike(part)));
2630
+ }
2631
+ /** Accepts string, or names an HTTP method as a literal in either case. */
2632
+ acceptsMethod(declared) {
2633
+ const slot = this.throughConstraint(declared);
2634
+ if (slot === VARIADIC)
2635
+ return false;
2636
+ if (this.acceptsString(slot))
2637
+ return true;
2638
+ return this.parts(slot).some(part => part.isStringLiteral() && HTTP_METHODS.has(asciiUpperCase(part.value)));
2639
+ }
2640
+ /**
2641
+ * A body parameter that takes any payload: `unknown`, or a type parameter
2642
+ * with no constraint (or one of `unknown` or `any`). `any` itself is not
2643
+ * open: a declared `any`, an unresolved type and a defaulted type argument
2644
+ * all read as `any`, and none of them says the parameter is a body.
2645
+ */
2646
+ isOpenBody(slot) {
2647
+ if (slot === VARIADIC)
2648
+ return false;
2649
+ const parts = this.parts(slot);
2650
+ return (parts.length > 0 &&
2651
+ parts.every(part => (part.flags & ts.TypeFlags.Unknown) !== 0 || this.isUnconstrained(part)));
2652
+ }
2653
+ /** Every part besides null and undefined is an object type. */
2654
+ isObjectType(slot) {
2655
+ if (slot === VARIADIC)
2656
+ return false;
2657
+ const parts = this.parts(slot);
2658
+ return parts.length > 0 && parts.every(part => this.isObjectLike(part));
2659
+ }
2660
+ isObjectLike(type) {
2661
+ if (type.flags & ts.TypeFlags.Object)
2662
+ return true;
2663
+ if (type.isIntersection())
2664
+ return type.types.every(part => this.isObjectLike(part));
2665
+ if (type.flags & ts.TypeFlags.TypeParameter) {
2666
+ const constraint = this.checker.getBaseConstraintOfType(type);
2667
+ return constraint !== undefined && constraint !== type && this.isObjectLike(constraint);
2668
+ }
2669
+ return false;
2670
+ }
2671
+ /**
2672
+ * The verdict when a slot fails its predicate: `unchecked` (`member_untyped`)
2673
+ * when its type says nothing, `failed` with `code` when it says something
2674
+ * else.
2675
+ */
2676
+ slotFailure(slot, code) {
2677
+ return this.saysNothing(slot) ? unchecked('member_untyped') : failed(code);
2678
+ }
2679
+ /**
2680
+ * Some part of the type, besides null and undefined, is `any`, `unknown`,
2681
+ * an unconstrained type parameter, or an object type with nothing declared
2682
+ * on it (`{}`, `Object`, `Function`).
2683
+ */
2684
+ saysNothing(slot) {
2685
+ if (slot === VARIADIC)
2686
+ return true;
2687
+ return this.parts(slot).some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part));
2688
+ }
2689
+ /**
2690
+ * What a factory builds says nothing: the type itself, or any branch of a
2691
+ * conditional return type, does.
2692
+ */
2693
+ returnSaysNothing(type) {
2694
+ if (this.saysNothing(type))
2695
+ return true;
2696
+ return this.parts(type).some(part => {
2697
+ if (!(part.flags & ts.TypeFlags.Conditional))
2698
+ return false;
2699
+ const node = part.root.node;
2700
+ return [node.trueType, node.falseType].some(branch => this.returnSaysNothing(this.checker.getTypeFromTypeNode(branch)));
2701
+ });
2702
+ }
2703
+ /** `any` (an unresolved type included) or `unknown`. */
2704
+ isOpenTop(type) {
2705
+ return (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
2706
+ }
2707
+ isUnconstrained(type) {
2708
+ if (!(type.flags & ts.TypeFlags.TypeParameter))
2709
+ return false;
2710
+ const constraint = this.checker.getBaseConstraintOfType(type);
2711
+ return constraint === undefined || this.isOpenTop(constraint);
2712
+ }
2713
+ isEmptyObject(type) {
2714
+ return ((type.flags & ts.TypeFlags.Object) !== 0 &&
2715
+ this.declaredProperties(type).length === 0 &&
2716
+ type.getCallSignatures().length === 0 &&
2717
+ type.getConstructSignatures().length === 0 &&
2718
+ this.checker.getIndexInfosOfType(type).length === 0);
2719
+ }
2720
+ /** The type's parts besides null and undefined. */
2721
+ parts(type) {
2722
+ return (type.isUnion() ? type.types : [type]).filter(part => !isNullish(part));
2723
+ }
2724
+ /**
2725
+ * What the signature has at parameter `index`, reading through a rest
2726
+ * parameter; `undefined` when it has none there. A rest typed by a type
2727
+ * parameter (`...rest: A`) is unreadable, unless `readConstraint` asks for
2728
+ * its element through the constraint (`A extends Array<X>` reads `X`).
2729
+ */
2730
+ parameterAt(signature, index, readConstraint = false) {
2731
+ const parameters = signature.getParameters();
2732
+ const last = parameters[parameters.length - 1];
2733
+ const declaration = last?.valueDeclaration;
2734
+ const restIndex = declaration && ts.isParameter(declaration) && declaration.dotDotDotToken
2735
+ ? parameters.length - 1
2736
+ : -1;
2737
+ if (restIndex === -1 || index < restIndex) {
2738
+ return index < parameters.length ? this.checker.getTypeOfSymbol(parameters[index]) : undefined;
2739
+ }
2740
+ let rest = this.checker.getTypeOfSymbol(last);
2741
+ if (readConstraint && rest.flags & ts.TypeFlags.TypeParameter) {
2742
+ rest = this.checker.getBaseConstraintOfType(rest) ?? rest;
2743
+ }
2744
+ if (this.checker.isTupleType(rest)) {
2745
+ return this.checker.getTypeArguments(rest)[index - restIndex];
2746
+ }
2747
+ if (this.checker.isArrayType(rest)) {
2748
+ return this.checker.getTypeArguments(rest)[0];
2749
+ }
2750
+ return this.isOpenTop(rest) ? rest : VARIADIC;
2751
+ }
2752
+ }
2753
+ /**
2754
+ * What `typeToString` asks the node builder for, less its length cut: a
2755
+ * union the builder cuts short keeps the members it made first, so the
2756
+ * listing orders every union before its own cut (`truncate`).
2757
+ */
2758
+ const PRINT_FLAGS = ts.NodeBuilderFlags.AllowUniqueESSymbolType |
2759
+ ts.NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope |
2760
+ ts.NodeBuilderFlags.IgnoreErrors |
2761
+ ts.NodeBuilderFlags.NoTruncation;
2762
+ const TYPE_PRINTER = ts.createPrinter({ removeComments: true });
2763
+ /**
2764
+ * A type node as `typeToString` prints it with no enclosing declaration: with
2765
+ * no source file, so a node the builder reused from a declaration prints its
2766
+ * own text.
2767
+ */
2768
+ function printTypeNode(node) {
2769
+ return TYPE_PRINTER.printNode(ts.EmitHint.Unspecified, node, undefined);
2770
+ }
2771
+ /** Code-unit order on names. */
2772
+ function byName(a, b) {
2773
+ return a < b ? -1 : a > b ? 1 : 0;
2774
+ }
2775
+ /**
2776
+ * Properties by name. A type's own property order is declaration order, but
2777
+ * a mapped type's (`Record<keyof M, F>`, `Partial<A & B>`) follows its key
2778
+ * union, in the order the compiler made the keys: listed as it comes, the
2779
+ * order would move with whatever the program read first.
2780
+ */
2781
+ function sortedByName(properties) {
2782
+ return [...properties].sort((a, b) => byName(a.getName(), b.getName()));
2783
+ }
2784
+ /** A printed type, cut to a length a listing can carry. */
2785
+ function truncate(text) {
2786
+ return text.length > 200 ? `${text.slice(0, 197)}...` : text;
2787
+ }
2788
+ /**
2789
+ * The package a specifier names (`@scope/name` or `name`, without a subpath)
2790
+ * is `packageName`, or `packageName` is its `@types` package
2791
+ * (`@types/scope__name` for a scoped one).
2792
+ */
2793
+ function isNamedPackage(specifier, packageName) {
2794
+ const named = packageNameOf(specifier);
2795
+ return packageName === named || packageName === typesPackageOf(named);
2796
+ }
2797
+ /** The `@types` package of a package name (`@types/scope__name` for a scoped one). */
2798
+ function typesPackageOf(named) {
2799
+ return `@types/${named.startsWith('@') ? named.slice(1).replace('/', '__') : named}`;
2800
+ }
2801
+ /** The package a specifier names: `@scope/name` or `name`, without a subpath. */
2802
+ function packageNameOf(specifier) {
2803
+ const segments = specifier.split('/');
2804
+ return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
2805
+ }
2806
+ function readInstalledPackage(host, directory, directoryName) {
2807
+ const manifest = path.join(directory, 'package.json');
2808
+ if (!host.fileExists(manifest))
2809
+ return null;
2810
+ let packageName;
2811
+ let version;
2812
+ try {
2813
+ const parsed = JSON.parse(host.readFile(manifest) ?? '');
2814
+ if (typeof parsed?.name === 'string')
2815
+ packageName = parsed.name;
2816
+ if (typeof parsed?.version === 'string')
2817
+ version = parsed.version;
2818
+ }
2819
+ catch {
2820
+ // An unreadable manifest still marks an installed directory; it names no package.
2821
+ }
2822
+ return { directoryName, packageName, version };
2823
+ }
2824
+ function isNullish(type) {
2825
+ return (type.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0;
2826
+ }
2827
+ /**
2828
+ * Keyed by a unique symbol. The checker escapes such a name as
2829
+ * `__@<description>@<symbol id>`; a string key that starts with `__` is
2830
+ * escaped with one more underscore, so it never matches.
2831
+ */
2832
+ function isSymbolKeyed(property) {
2833
+ return property.escapedName.startsWith('__@');
2834
+ }
2835
+ /** `string`, a string literal or template, or an intersection with one (`string & {}`). */
2836
+ function isStringLike(type) {
2837
+ if (type.flags & ts.TypeFlags.StringLike)
2838
+ return true;
2839
+ return type.isIntersection() && type.types.some(isStringLike);
2840
+ }