carrick 0.3.126 → 0.3.128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.js +5 -1
- package/sidecar/dist/src/capture/check-classify.d.ts +8 -1
- package/sidecar/dist/src/capture/check-classify.js +21 -1
- package/sidecar/dist/src/capture/check-probe.d.ts +7 -7
- package/sidecar/dist/src/capture/check-probe.js +13 -14
- package/sidecar/dist/src/capture/check.js +5 -5
- package/sidecar/dist/src/index.js +56 -4
- package/sidecar/dist/src/library-claims.d.ts +63 -3
- package/sidecar/dist/src/library-claims.js +435 -44
- package/sidecar/dist/src/retype.js +124 -8
- package/sidecar/dist/src/server-labels.d.ts +29 -0
- package/sidecar/dist/src/server-labels.js +45 -0
- package/sidecar/dist/src/stated-params.d.ts +138 -0
- package/sidecar/dist/src/stated-params.js +547 -0
- package/sidecar/dist/src/type-inferrer.d.ts +144 -8
- package/sidecar/dist/src/type-inferrer.js +635 -35
- package/sidecar/dist/src/types.d.ts +84 -3
- package/sidecar/dist/src/unwidened.d.ts +11 -2
- package/sidecar/dist/src/unwidened.js +50 -3
- package/sidecar/dist/src/validators.d.ts +228 -59
- package/sidecar/dist/src/validators.js +45 -2
|
@@ -130,6 +130,14 @@ const HTTP_V2_RULES = { http: false, message: true, ops: new Set(['request']), m
|
|
|
130
130
|
* the method from the member's own name (`get` registers GET) or from a slot.
|
|
131
131
|
*/
|
|
132
132
|
const SERVER_RULES = { http: false, message: true, ops: new Set(['receive']), methods: ROUTE_METHODS };
|
|
133
|
+
/**
|
|
134
|
+
* A server's rules as `labelledMembers` reads them (carrick#2674): a `send`
|
|
135
|
+
* op is the reply a handler sends back through its reply parameter, on the
|
|
136
|
+
* connection the request came in on, so it names nothing on the wire. Only
|
|
137
|
+
* the stated-parameter reading takes these; `run` keeps `SERVER_RULES`, so
|
|
138
|
+
* no verdict the scanner reads rows from changes.
|
|
139
|
+
*/
|
|
140
|
+
const SERVER_REPLY_RULES = { ...SERVER_RULES, ops: new Set(['receive', 'send']), namelessSends: true };
|
|
133
141
|
/**
|
|
134
142
|
* The role table: the role alone picks the checks. A role this build does
|
|
135
143
|
* not read yet (GraphQL executors) answers `role_unsupported`, and `none`
|
|
@@ -263,7 +271,8 @@ export class LibraryClaimsVerifier {
|
|
|
263
271
|
* A check on an instance or a scope reads the maker or scope claims of the
|
|
264
272
|
* same request first, whatever their place in it.
|
|
265
273
|
*/
|
|
266
|
-
run(fromDir, checks, budgetMs, scanRoot) {
|
|
274
|
+
run(fromDir, checks, budgetMs, scanRoot, options = {}) {
|
|
275
|
+
const { observe, readReplies = false, typePackages = {} } = options;
|
|
267
276
|
const deadline = performance.now() + budgetMs;
|
|
268
277
|
const stamp = (check, outcome) => outcome.verdict === 'verified'
|
|
269
278
|
? {
|
|
@@ -294,7 +303,11 @@ export class LibraryClaimsVerifier {
|
|
|
294
303
|
if (check.role === 'http_client' && hasV2Field(check.claim))
|
|
295
304
|
v2Exports.add(exportKey(check));
|
|
296
305
|
}
|
|
297
|
-
const rulesFor = (check) => check.role === 'http_client' && v2Exports.has(exportKey(check))
|
|
306
|
+
const rulesFor = (check) => check.role === 'http_client' && v2Exports.has(exportKey(check))
|
|
307
|
+
? HTTP_V2_RULES
|
|
308
|
+
: readReplies && check.role === 'server_framework'
|
|
309
|
+
? SERVER_REPLY_RULES
|
|
310
|
+
: ROLE_TABLE[check.role];
|
|
298
311
|
for (const check of checks) {
|
|
299
312
|
const key = exportKey(check);
|
|
300
313
|
if (!importIndex.has(key)) {
|
|
@@ -448,14 +461,20 @@ export class LibraryClaimsVerifier {
|
|
|
448
461
|
if (types.some(type => reader.returnSaysNothing(type))) {
|
|
449
462
|
return { reason: receiverPath.maker ? 'maker_unresolved' : 'export_untyped' };
|
|
450
463
|
}
|
|
464
|
+
const claim = check.claim;
|
|
465
|
+
const keyBound = claim.kind === 'op' && claim.name !== undefined && 'bound' in claim.name && claim.name.bound === 'key'
|
|
466
|
+
? verifiedCollects(check.package).length > 0
|
|
467
|
+
: undefined;
|
|
451
468
|
return {
|
|
452
469
|
value: types.map(type => ({
|
|
453
470
|
type,
|
|
454
471
|
pkg: check.package,
|
|
455
|
-
home: reader.homeOf(check.package, [exported.target, ...typeSymbols(type)]),
|
|
472
|
+
home: withTypePackages(reader.homeOf(check.package, [exported.target, ...typeSymbols(type)]), typePackages[check.package]),
|
|
456
473
|
makerBindsName,
|
|
457
474
|
scoped: receiverPath.scope !== undefined,
|
|
458
475
|
makerHolding,
|
|
476
|
+
...(keyBound === undefined ? {} : { keyBound }),
|
|
477
|
+
madeBy: receiverPath.maker?.member === null && receiverPath.scope === undefined ? check.export : undefined,
|
|
459
478
|
})),
|
|
460
479
|
};
|
|
461
480
|
};
|
|
@@ -479,6 +498,32 @@ export class LibraryClaimsVerifier {
|
|
|
479
498
|
return { reason: 'unresolved' };
|
|
480
499
|
return { value: returns };
|
|
481
500
|
};
|
|
501
|
+
/**
|
|
502
|
+
* The `collects` scope checks of the request (#2474 R6a) that verify,
|
|
503
|
+
* of one package or of any: judged only when a claim reads them, so a
|
|
504
|
+
* request with none judges exactly what it judged before.
|
|
505
|
+
*/
|
|
506
|
+
const verifiedCollects = (pkg) => checks
|
|
507
|
+
.map((other, i) => ({ other, i }))
|
|
508
|
+
.filter(({ other }) => other.claim.kind === 'scope' && other.claim.collects !== undefined && (pkg === undefined || other.package === pkg))
|
|
509
|
+
.filter(({ i }) => judge(i).outcome.verdict === 'verified')
|
|
510
|
+
.map(({ i }) => i);
|
|
511
|
+
/**
|
|
512
|
+
* What the request's verified `collects` scopes return, read at their
|
|
513
|
+
* defaults: the collections an adapter's `runs` slot can take, matched
|
|
514
|
+
* across packages (an HTTP framework's adapter serves another
|
|
515
|
+
* package's router). Undefined when there are none.
|
|
516
|
+
*/
|
|
517
|
+
const collections = () => {
|
|
518
|
+
const types = new Set();
|
|
519
|
+
for (const i of verifiedCollects()) {
|
|
520
|
+
const built = builtReceiver(checks[i]);
|
|
521
|
+
const read = holdingReturns([judge(i)], built === undefined ? undefined : atDefaults(checks[i], built));
|
|
522
|
+
if ('value' in read)
|
|
523
|
+
read.value.forEach(type => types.add(type));
|
|
524
|
+
}
|
|
525
|
+
return types.size > 0 ? [...types] : undefined;
|
|
526
|
+
};
|
|
482
527
|
const judge = (index) => {
|
|
483
528
|
const cached = judged.get(index);
|
|
484
529
|
if (cached)
|
|
@@ -534,16 +579,28 @@ export class LibraryClaimsVerifier {
|
|
|
534
579
|
return { outcome: unchecked(receivers.reason) };
|
|
535
580
|
const built = builtReceiver(check);
|
|
536
581
|
const builtAtDefaults = built === undefined ? undefined : atDefaults(check, built);
|
|
582
|
+
// An adapter's `runs` is read against the request's collections only
|
|
583
|
+
// when today's reading refuses it (#2474 R6a): `judgeMake` asks.
|
|
584
|
+
const collectionsOf = check.claim.kind === 'make' && check.claim.runs !== undefined ? collections : undefined;
|
|
537
585
|
const holding = [];
|
|
538
586
|
const declarations = [];
|
|
587
|
+
const members = [];
|
|
588
|
+
const bodyMembers = [];
|
|
539
589
|
for (const receiver of receivers.value) {
|
|
540
|
-
const result = reader.judgeMessage(receiver, check.claim, rules, builtAtDefaults);
|
|
590
|
+
const result = reader.judgeMessage(receiver, check.claim, rules, builtAtDefaults, collectionsOf);
|
|
541
591
|
if (result.outcome.verdict !== 'verified')
|
|
542
592
|
return result;
|
|
543
593
|
holding.push(...(result.holding ?? []));
|
|
544
594
|
declarations.push(...(result.outcome.declarations ?? []));
|
|
595
|
+
members.push(...(result.outcome.members ?? []));
|
|
596
|
+
bodyMembers.push(...(result.outcome.bodyMembers ?? []));
|
|
545
597
|
}
|
|
546
|
-
|
|
598
|
+
const verified = declarations.length > 0 ? { verdict: 'verified', declarations: sortedDeclarations(declarations) } : { ...VERIFIED };
|
|
599
|
+
if (verified.verdict === 'verified' && members.length > 0)
|
|
600
|
+
verified.members = members;
|
|
601
|
+
if (verified.verdict === 'verified' && bodyMembers.length > 0)
|
|
602
|
+
verified.bodyMembers = bodyMembers;
|
|
603
|
+
return { outcome: verified, holding };
|
|
547
604
|
};
|
|
548
605
|
const semantics = [];
|
|
549
606
|
for (let index = 0; index < checks.length; index++) {
|
|
@@ -552,7 +609,9 @@ export class LibraryClaimsVerifier {
|
|
|
552
609
|
semantics.push(stamp(check, unchecked('budget')));
|
|
553
610
|
continue;
|
|
554
611
|
}
|
|
555
|
-
|
|
612
|
+
const outcome = judge(index).outcome;
|
|
613
|
+
observe?.(check, outcome);
|
|
614
|
+
semantics.push(stamp(check, outcome));
|
|
556
615
|
}
|
|
557
616
|
return {
|
|
558
617
|
semantics,
|
|
@@ -563,6 +622,42 @@ export class LibraryClaimsVerifier {
|
|
|
563
622
|
this.project.removeSourceFile(probe);
|
|
564
623
|
}
|
|
565
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* The members a service's server-framework labels name, read with the
|
|
627
|
+
* verifier itself so only a label that verifies in the service's own
|
|
628
|
+
* program counts (carrick#2674): each verified `send` op that carries a
|
|
629
|
+
* `payload` slot, and each verified `receive` op whose `request` names a
|
|
630
|
+
* `body` path. Other checks are not judged.
|
|
631
|
+
*/
|
|
632
|
+
labelledMembers(fromDir, checks, budgetMs, scanRoot, typePackages = {}) {
|
|
633
|
+
const sends = new Map();
|
|
634
|
+
const bodies = new Set();
|
|
635
|
+
const wanted = checks.filter(check => check.role === 'server_framework' &&
|
|
636
|
+
check.claim.kind === 'op' &&
|
|
637
|
+
((check.claim.op === 'send' && check.claim.payload !== undefined) ||
|
|
638
|
+
(check.claim.op === 'receive' && check.claim.request?.body !== undefined)));
|
|
639
|
+
// A receiver an op is read on can need the maker or scope claims beside it.
|
|
640
|
+
const context = checks.filter(check => check.role === 'server_framework' && (check.claim.kind === 'make' || check.claim.kind === 'scope'));
|
|
641
|
+
if (wanted.length === 0)
|
|
642
|
+
return { sends, bodies };
|
|
643
|
+
const outcomes = {};
|
|
644
|
+
const observe = (check, outcome) => {
|
|
645
|
+
if (check.claim.kind !== 'op')
|
|
646
|
+
return;
|
|
647
|
+
const read = outcome.verdict === 'verified' ? 'verified' : `${outcome.verdict} ${outcome.reason}`;
|
|
648
|
+
outcomes[read] = (outcomes[read] ?? 0) + 1;
|
|
649
|
+
if (outcome.verdict !== 'verified')
|
|
650
|
+
return;
|
|
651
|
+
if (check.claim.op === 'send' && check.claim.payload !== undefined) {
|
|
652
|
+
for (const member of outcome.members ?? [])
|
|
653
|
+
sends.set(declarationKey(member), check.claim.payload);
|
|
654
|
+
}
|
|
655
|
+
for (const member of outcome.bodyMembers ?? [])
|
|
656
|
+
bodies.add(declarationKey(member));
|
|
657
|
+
};
|
|
658
|
+
this.run(fromDir, [...context, ...wanted], budgetMs, scanRoot, { observe, readReplies: true, typePackages });
|
|
659
|
+
return { sends, bodies, outcomes };
|
|
660
|
+
}
|
|
566
661
|
/**
|
|
567
662
|
* Each specifier's declared surface, read with the verifier's predicates
|
|
568
663
|
* (carrick#1660): every value export (or only those `only` names for the
|
|
@@ -667,7 +762,8 @@ function resolvedBuild(checker, statement) {
|
|
|
667
762
|
* array of `[package, exports]` for every surface that listed at least one
|
|
668
763
|
* export, sorted by `package` (code unit order). A specifier that lists
|
|
669
764
|
* nothing (unresolved, local, no value exports) is left out, so asking for a
|
|
670
|
-
* subpath a version does not have changes nothing.
|
|
765
|
+
* subpath a version does not have changes nothing. Handler members are not
|
|
766
|
+
* hashed. Type text already names
|
|
671
767
|
* the request's directory as `<root>`.
|
|
672
768
|
*/
|
|
673
769
|
export function fullSurfaceSha256(surfaces) {
|
|
@@ -675,7 +771,10 @@ export function fullSurfaceSha256(surfaces) {
|
|
|
675
771
|
.filter(surface => surface.reason === undefined && surface.exports.length > 0)
|
|
676
772
|
.map(surface => [surface.package, surface.exports])
|
|
677
773
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
678
|
-
|
|
774
|
+
// A member's `handlers` (carrick#2674) is out of the hash at every depth, so
|
|
775
|
+
// a package's key does not change when the lister learns to list them.
|
|
776
|
+
const text = JSON.stringify(listed, (key, value) => (key === 'handlers' ? undefined : value));
|
|
777
|
+
return crypto.createHash('sha256').update(text).digest('hex');
|
|
679
778
|
}
|
|
680
779
|
function exportKey(check) {
|
|
681
780
|
return JSON.stringify([check.package, check.export]);
|
|
@@ -732,17 +831,31 @@ function placementInvalid(claim) {
|
|
|
732
831
|
return of === undefined || (of.maker === undefined && of.scope === undefined);
|
|
733
832
|
}
|
|
734
833
|
/**
|
|
735
|
-
* Where a handler's request holds its path and
|
|
736
|
-
* a receive op with a handler, and names `path
|
|
737
|
-
* it gives is non-empty.
|
|
834
|
+
* Where a handler's request holds its path, method and body (#2474 R0,
|
|
835
|
+
* carrick#2674) is read on a receive op with a handler, and names `path`,
|
|
836
|
+
* `url` or `body`; every member path it gives is non-empty.
|
|
738
837
|
*/
|
|
739
838
|
function requestAtValid(claim) {
|
|
740
839
|
const request = claim.request;
|
|
741
840
|
if (claim.op !== 'receive' || claim.handler === undefined)
|
|
742
841
|
return false;
|
|
743
|
-
if (request.path === undefined && request.url === undefined)
|
|
842
|
+
if (request.path === undefined && request.url === undefined && request.body === undefined)
|
|
744
843
|
return false;
|
|
745
|
-
return [request.path, request.url, request.method].every(members => members === undefined || members.length > 0);
|
|
844
|
+
return [request.path, request.url, request.method, request.body].every(members => members === undefined || members.length > 0);
|
|
845
|
+
}
|
|
846
|
+
/** A receiver's home packages, with the type-only packages the store lists for its package. */
|
|
847
|
+
function withTypePackages(home, typePackages) {
|
|
848
|
+
return typePackages && typePackages.length > 0 ? new Set([...home, ...typePackages]) : home;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* A declaration's key within one process: its file as the compiler names it
|
|
852
|
+
* and its start. Two programs of one project name a declaration alike, so a
|
|
853
|
+
* label read in the verifier's probe program joins a member read in the
|
|
854
|
+
* inferrer's (carrick#2674).
|
|
855
|
+
*/
|
|
856
|
+
export function declarationKey(declaration) {
|
|
857
|
+
const file = declaration.getSourceFile();
|
|
858
|
+
return `${file.fileName}:${declaration.getStart(file)}`;
|
|
746
859
|
}
|
|
747
860
|
/**
|
|
748
861
|
* A message check names a receiver its claim can be read on (contract
|
|
@@ -1501,10 +1614,10 @@ class DeclarationReader {
|
|
|
1501
1614
|
* overloads it holds on. `built` is the probe's no-argument build of the
|
|
1502
1615
|
* receiver a maker or scope claim makes (see `returnOf`).
|
|
1503
1616
|
*/
|
|
1504
|
-
judgeMessage(receiver, claim, rules, built) {
|
|
1617
|
+
judgeMessage(receiver, claim, rules, built, collections) {
|
|
1505
1618
|
switch (claim.kind) {
|
|
1506
1619
|
case 'make':
|
|
1507
|
-
return this.judgeMake(receiver, claim, built);
|
|
1620
|
+
return this.judgeMake(receiver, claim, built, collections);
|
|
1508
1621
|
case 'scope':
|
|
1509
1622
|
return this.judgeScope(receiver, claim, built);
|
|
1510
1623
|
case 'op':
|
|
@@ -1522,8 +1635,15 @@ class DeclarationReader {
|
|
|
1522
1635
|
* defaults (`returnOf`). Those overloads are the ones an instance is read
|
|
1523
1636
|
* through. A definition (`task({ id, run })`) is a maker with a `name` and a
|
|
1524
1637
|
* `handler` slot; a queue (`new Queue("emails")`) one with a positional name.
|
|
1525
|
-
|
|
1526
|
-
|
|
1638
|
+
*
|
|
1639
|
+
* An adapter (#2474 R6a, contract amendment 3 on carrick#2434) is a maker
|
|
1640
|
+
* whose `runs` slot takes a collection rather than a function. Today's
|
|
1641
|
+
* reading runs first, unchanged; only an overload it refuses on `runs` is
|
|
1642
|
+
* read again, when the request holds verified `collects` scopes
|
|
1643
|
+
* (`collections`), with `runs` taking what one of them returns
|
|
1644
|
+
* (`runsCollectionOutcome`). With no such scope this arm never runs.
|
|
1645
|
+
*/
|
|
1646
|
+
judgeMake(receiver, claim, built, collections) {
|
|
1527
1647
|
const layout = this.layout({
|
|
1528
1648
|
base: claim.base,
|
|
1529
1649
|
prefix: claim.prefix,
|
|
@@ -1541,6 +1661,12 @@ class DeclarationReader {
|
|
|
1541
1661
|
let best;
|
|
1542
1662
|
for (const signature of callee.signatures) {
|
|
1543
1663
|
let result = this.messageSignatureOutcome(signature, layout.value, labels);
|
|
1664
|
+
if (result.outcome.verdict !== 'verified' && result.outcome.reason.startsWith('runs_') && collections !== undefined) {
|
|
1665
|
+
const collected = collections();
|
|
1666
|
+
if (collected !== undefined) {
|
|
1667
|
+
result = this.messageSignatureOutcome(signature, layout.value, labels, declared => this.runsCollectionOutcome(signature, declared, collected));
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1544
1670
|
if (result.rank === Infinity && this.returnSaysNothing(this.returnOf(signature, built))) {
|
|
1545
1671
|
result = { rank: 6, outcome: unchecked('maker_unresolved') };
|
|
1546
1672
|
}
|
|
@@ -1569,12 +1695,23 @@ class DeclarationReader {
|
|
|
1569
1695
|
* overloads: a `>scope:` receiver read through it is not resolved.
|
|
1570
1696
|
* `runs` without `yields` (or the reverse), or `mounts` beside either, is
|
|
1571
1697
|
* `claim_invalid`.
|
|
1698
|
+
*
|
|
1699
|
+
* Procedure routers (#2474 R6a, contract amendment 3 on carrick#2434): a
|
|
1700
|
+
* scope carries exactly one of `name` and `collects`, and `collects` never
|
|
1701
|
+
* beside `runs`, `yields` or `mounts` (`claim_invalid`). With `collects`
|
|
1702
|
+
* the member takes an object of procedures there (`collectsOutcome`), and
|
|
1703
|
+
* its return, the collection, is read as a plain scope's is.
|
|
1572
1704
|
*/
|
|
1573
1705
|
judgeScope(receiver, claim, built) {
|
|
1574
1706
|
if ((claim.runs === undefined) !== (claim.yields === undefined))
|
|
1575
1707
|
return { outcome: unchecked('claim_invalid') };
|
|
1576
1708
|
if (claim.mounts !== undefined && claim.runs !== undefined)
|
|
1577
1709
|
return { outcome: unchecked('claim_invalid') };
|
|
1710
|
+
if ((claim.name === undefined) === (claim.collects === undefined))
|
|
1711
|
+
return { outcome: unchecked('claim_invalid') };
|
|
1712
|
+
if (claim.collects !== undefined && (claim.runs !== undefined || claim.mounts !== undefined)) {
|
|
1713
|
+
return { outcome: unchecked('claim_invalid') };
|
|
1714
|
+
}
|
|
1578
1715
|
const binds = claim.runs !== undefined || claim.mounts !== undefined;
|
|
1579
1716
|
const layout = this.layout({ name: claim.name, runs: claim.runs, mounts: claim.mounts });
|
|
1580
1717
|
if ('failure' in layout)
|
|
@@ -1597,6 +1734,11 @@ class DeclarationReader {
|
|
|
1597
1734
|
if (mounted)
|
|
1598
1735
|
result = { rank: 5, outcome: mounted };
|
|
1599
1736
|
}
|
|
1737
|
+
if (result.rank === Infinity && claim.collects !== undefined) {
|
|
1738
|
+
const collected = this.collectsOutcome(signature, claim.collects);
|
|
1739
|
+
if (collected)
|
|
1740
|
+
result = { rank: 5, outcome: collected };
|
|
1741
|
+
}
|
|
1600
1742
|
if (result.rank === Infinity && !binds && this.returnSaysNothing(this.returnOf(signature, built))) {
|
|
1601
1743
|
result = { rank: 6, outcome: unchecked('scope_unresolved') };
|
|
1602
1744
|
}
|
|
@@ -1645,6 +1787,63 @@ class DeclarationReader {
|
|
|
1645
1787
|
return erased ? undefined : failed('mounts_not_instance');
|
|
1646
1788
|
return this.checker.isTypeAssignableTo(receiver.type, slot) ? undefined : failed('mounts_not_instance');
|
|
1647
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* `collects: { arg, key?, join }` on a scope (#2474 R6a): the parameter
|
|
1792
|
+
* there is an object type that is not a function, whose keys name the
|
|
1793
|
+
* procedures. A parameter typed `any`, `unknown`, `{}` or by an
|
|
1794
|
+
* unconstrained type parameter says nothing (`collects_untyped`).
|
|
1795
|
+
* Undefined when it holds.
|
|
1796
|
+
*/
|
|
1797
|
+
collectsOutcome(signature, collects) {
|
|
1798
|
+
let declared = this.keyParameterAt(signature, collects.arg);
|
|
1799
|
+
if (declared !== undefined && collects.key !== undefined) {
|
|
1800
|
+
const property = this.declaredProperty(declared, collects.key);
|
|
1801
|
+
declared = property ? this.throughConstraint(this.checker.getTypeOfSymbol(property)) : undefined;
|
|
1802
|
+
}
|
|
1803
|
+
if (declared === undefined)
|
|
1804
|
+
return failed('param_missing');
|
|
1805
|
+
if (declared === VARIADIC)
|
|
1806
|
+
return unchecked('collects_untyped');
|
|
1807
|
+
const parts = this.parts(declared);
|
|
1808
|
+
if (parts.length === 0)
|
|
1809
|
+
return failed('collects_not_object');
|
|
1810
|
+
if (parts.some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part))) {
|
|
1811
|
+
return unchecked('collects_untyped');
|
|
1812
|
+
}
|
|
1813
|
+
if (parts.some(part => !this.isObjectLike(part) || part.getCallSignatures().length > 0)) {
|
|
1814
|
+
return failed('collects_not_object');
|
|
1815
|
+
}
|
|
1816
|
+
return undefined;
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* An adapter's `runs` slot (#2474 R6a) takes one of `collections`, the
|
|
1820
|
+
* types the request's verified `collects` scopes return, read as a mounted
|
|
1821
|
+
* instance is (`fitsErased`, then assignability). A slot typed `any`,
|
|
1822
|
+
* `unknown`, `{}` or by an unconstrained type parameter says nothing
|
|
1823
|
+
* (`runs_untyped`). Undefined when it holds.
|
|
1824
|
+
*/
|
|
1825
|
+
runsCollectionOutcome(signature, declared, collections) {
|
|
1826
|
+
if (declared === undefined)
|
|
1827
|
+
return failed('param_missing');
|
|
1828
|
+
const slot = this.throughConstraint(declared);
|
|
1829
|
+
if (slot === VARIADIC)
|
|
1830
|
+
return unchecked('runs_untyped');
|
|
1831
|
+
const parts = this.parts(slot);
|
|
1832
|
+
if (parts.length === 0)
|
|
1833
|
+
return failed('runs_not_collection');
|
|
1834
|
+
if (parts.some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part))) {
|
|
1835
|
+
return unchecked('runs_untyped');
|
|
1836
|
+
}
|
|
1837
|
+
const own = signature.typeParameters ?? [];
|
|
1838
|
+
for (const collection of collections) {
|
|
1839
|
+
const erased = this.fitsErased(collection, slot, own);
|
|
1840
|
+
if (erased === true)
|
|
1841
|
+
return undefined;
|
|
1842
|
+
if (erased === undefined && this.checker.isTypeAssignableTo(collection, slot))
|
|
1843
|
+
return undefined;
|
|
1844
|
+
}
|
|
1845
|
+
return failed('runs_not_collection');
|
|
1846
|
+
}
|
|
1648
1847
|
/**
|
|
1649
1848
|
* `receiver` fits `slot` with the signature's own type parameters
|
|
1650
1849
|
* (`own`) erased to their constraints (carrick#2652), or undefined when
|
|
@@ -1735,6 +1934,15 @@ class DeclarationReader {
|
|
|
1735
1934
|
return true;
|
|
1736
1935
|
if (type.isUnionOrIntersection())
|
|
1737
1936
|
return type.types.some(part => this.mentions(part, parameter, depth + 1));
|
|
1937
|
+
// A deferred conditional (`Server extends X ? A : B`) or an alias
|
|
1938
|
+
// instance names its parameter in its check type or its alias arguments
|
|
1939
|
+
// (carrick#2663).
|
|
1940
|
+
if (type.aliasTypeArguments?.some(argument => this.mentions(argument, parameter, depth + 1)))
|
|
1941
|
+
return true;
|
|
1942
|
+
if (type.flags & ts.TypeFlags.Conditional) {
|
|
1943
|
+
const conditional = type;
|
|
1944
|
+
return [conditional.checkType, conditional.extendsType].some(part => this.mentions(part, parameter, depth + 1));
|
|
1945
|
+
}
|
|
1738
1946
|
const reference = this.referenceOf(type);
|
|
1739
1947
|
if (reference !== undefined) {
|
|
1740
1948
|
return this.checker.getTypeArguments(reference).some(argument => this.mentions(argument, parameter, depth + 1));
|
|
@@ -1766,7 +1974,7 @@ class DeclarationReader {
|
|
|
1766
1974
|
// what it runs, and one that hands its handler a receiver (a server's
|
|
1767
1975
|
// connection event) names nothing on the wire itself, so neither needs a
|
|
1768
1976
|
// name of its own.
|
|
1769
|
-
const nameless = claim.runs !== undefined || claim.yields !== undefined;
|
|
1977
|
+
const nameless = claim.runs !== undefined || claim.yields !== undefined || (claim.op === 'send' && rules.namelessSends === true);
|
|
1770
1978
|
if ((name === undefined && !nameless) || (claim.op === 'send' && claim.payload === undefined)) {
|
|
1771
1979
|
return unchecked('claim_invalid');
|
|
1772
1980
|
}
|
|
@@ -1775,7 +1983,8 @@ class DeclarationReader {
|
|
|
1775
1983
|
nameSlot = undefined;
|
|
1776
1984
|
}
|
|
1777
1985
|
else if ('bound' in name) {
|
|
1778
|
-
|
|
1986
|
+
const bound = name.bound === 'maker' ? receiver.makerBindsName : name.bound === 'scope' ? receiver.scoped : receiver.keyBound === true;
|
|
1987
|
+
if (!bound)
|
|
1779
1988
|
return failed('name_unbound');
|
|
1780
1989
|
}
|
|
1781
1990
|
else {
|
|
@@ -1802,6 +2011,7 @@ class DeclarationReader {
|
|
|
1802
2011
|
const labels = claim.key_labels ?? {};
|
|
1803
2012
|
let best;
|
|
1804
2013
|
let held = false;
|
|
2014
|
+
const bodyMembers = [];
|
|
1805
2015
|
for (const signature of callee.signatures) {
|
|
1806
2016
|
let result = this.messageSignatureOutcome(signature, layout.value, labels);
|
|
1807
2017
|
if (result.rank === Infinity && claim.yields !== undefined) {
|
|
@@ -1810,7 +2020,7 @@ class DeclarationReader {
|
|
|
1810
2020
|
result = { rank: 5, outcome: given };
|
|
1811
2021
|
}
|
|
1812
2022
|
if (result.rank === Infinity && claim.request !== undefined) {
|
|
1813
|
-
const read = this.requestOutcome(signature, claim.handler, claim.request);
|
|
2023
|
+
const read = this.requestOutcome(signature, claim.handler, claim.request, bodyMembers);
|
|
1814
2024
|
if (read)
|
|
1815
2025
|
result = { rank: 5, outcome: read };
|
|
1816
2026
|
}
|
|
@@ -1825,7 +2035,15 @@ class DeclarationReader {
|
|
|
1825
2035
|
if (boundRuns)
|
|
1826
2036
|
return this.boundRunsOutcome(receiver.makerHolding, boundRuns);
|
|
1827
2037
|
const declarations = this.declarationsOf(callee);
|
|
1828
|
-
|
|
2038
|
+
const members = callee.named
|
|
2039
|
+
? (callee.named.property.declarations ?? [])
|
|
2040
|
+
: callee.signatures.flatMap(signature => signature.getDeclaration() ?? []);
|
|
2041
|
+
return {
|
|
2042
|
+
verdict: 'verified',
|
|
2043
|
+
...(declarations.length > 0 ? { declarations } : {}),
|
|
2044
|
+
...(members.length > 0 ? { members } : {}),
|
|
2045
|
+
...(bodyMembers.length > 0 ? { bodyMembers } : {}),
|
|
2046
|
+
};
|
|
1829
2047
|
}
|
|
1830
2048
|
/**
|
|
1831
2049
|
* Where a verified op or scope's member is declared, as position rows spell
|
|
@@ -1872,6 +2090,18 @@ class DeclarationReader {
|
|
|
1872
2090
|
*/
|
|
1873
2091
|
methodOutcome(claim, rules) {
|
|
1874
2092
|
const words = rules.methods;
|
|
2093
|
+
// A procedure named by its key (#2474 R6a): a receive op with a handler
|
|
2094
|
+
// whose label states its method, a route word. The member names the
|
|
2095
|
+
// procedure kind (`query`), not a verb, so it need not equal the method.
|
|
2096
|
+
if (claim.name !== undefined && 'bound' in claim.name && claim.name.bound === 'key') {
|
|
2097
|
+
if (!words || claim.op !== 'receive' || claim.handler === undefined || claim.method === undefined) {
|
|
2098
|
+
return unchecked('claim_invalid');
|
|
2099
|
+
}
|
|
2100
|
+
if (claim.method_key !== undefined || claim.method_default !== undefined || claim.options !== undefined) {
|
|
2101
|
+
return unchecked('claim_invalid');
|
|
2102
|
+
}
|
|
2103
|
+
return words.has(claim.method) ? undefined : failed('method_not_word');
|
|
2104
|
+
}
|
|
1875
2105
|
if (!words) {
|
|
1876
2106
|
const any = claim.method !== undefined ||
|
|
1877
2107
|
claim.method_key !== undefined ||
|
|
@@ -1927,11 +2157,17 @@ class DeclarationReader {
|
|
|
1927
2157
|
return failure.verdict === 'failed' ? failed(reason) : unchecked(reason);
|
|
1928
2158
|
}
|
|
1929
2159
|
/**
|
|
1930
|
-
* `yields: { param, export, side }` on a receive op
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
1934
|
-
*
|
|
2160
|
+
* `yields: { param, export, side }` on a receive op or a scope: some
|
|
2161
|
+
* signature of the function this overload takes declares a parameter at
|
|
2162
|
+
* `param` whose type is either
|
|
2163
|
+
* - the package's own export named `export` (a class, or the interface an
|
|
2164
|
+
* instance has), declared in the receiver's home packages; or
|
|
2165
|
+
* - when `export` is the maker whose own call made the instance the claim
|
|
2166
|
+
* is read on (`madeBy`, carrick#2663): that instance, compared by
|
|
2167
|
+
* declaration (`handsInstance`), since a factory's result has no export
|
|
2168
|
+
* name of its own.
|
|
2169
|
+
* `side` is `client` or `server` (`yields_side_invalid`). Undefined when it
|
|
2170
|
+
* holds.
|
|
1935
2171
|
*/
|
|
1936
2172
|
yieldsOutcome(signature, handler, yields, receiver) {
|
|
1937
2173
|
if (yields.side !== 'client' && yields.side !== 'server')
|
|
@@ -1955,18 +2191,53 @@ class DeclarationReader {
|
|
|
1955
2191
|
(symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home)));
|
|
1956
2192
|
if (named)
|
|
1957
2193
|
return undefined;
|
|
2194
|
+
if (yields.export === receiver.madeBy && this.handsInstance(given, receiver, signature.typeParameters ?? [])) {
|
|
2195
|
+
return undefined;
|
|
2196
|
+
}
|
|
1958
2197
|
}
|
|
1959
2198
|
}
|
|
1960
2199
|
return failed('yields_not_export');
|
|
1961
2200
|
}
|
|
1962
2201
|
/**
|
|
1963
|
-
* `
|
|
1964
|
-
* some
|
|
1965
|
-
*
|
|
1966
|
-
*
|
|
1967
|
-
*
|
|
2202
|
+
* `given` (each part, nullish dropped) is the instance `receiver` holds:
|
|
2203
|
+
* some part of its intersection is a class or interface the receiver's
|
|
2204
|
+
* home packages declare, and the same declaration (the same symbol, never
|
|
2205
|
+
* the same name) as a part of the instance's own intersection. A generic
|
|
2206
|
+
* instance is built at its defaults while `given` threads the overload's
|
|
2207
|
+
* own type parameters (`own`) through its arguments, so the arguments are
|
|
2208
|
+
* read as `fitsErased` reads a mounted instance (carrick#2652): a
|
|
2209
|
+
* definite mismatch refuses, an argument it cannot read leaves the
|
|
2210
|
+
* declaration to decide.
|
|
2211
|
+
*/
|
|
2212
|
+
handsInstance(given, receiver, own) {
|
|
2213
|
+
const declared = (type) => {
|
|
2214
|
+
const symbol = type.getSymbol();
|
|
2215
|
+
if (!symbol || !(symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)))
|
|
2216
|
+
return undefined;
|
|
2217
|
+
const home = (symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home));
|
|
2218
|
+
return home ? symbol : undefined;
|
|
2219
|
+
};
|
|
2220
|
+
const intersected = (type) => (type.isIntersection() ? type.types : [type]);
|
|
2221
|
+
const instances = this.parts(receiver.type)
|
|
2222
|
+
.flatMap(intersected)
|
|
2223
|
+
.filter(part => declared(part) !== undefined);
|
|
2224
|
+
const parts = this.parts(given);
|
|
2225
|
+
return (parts.length > 0 &&
|
|
2226
|
+
parts.every(part => intersected(part).some(handed => {
|
|
2227
|
+
const symbol = declared(handed);
|
|
2228
|
+
return (symbol !== undefined &&
|
|
2229
|
+
instances.some(instance => declared(instance) === symbol && this.fitsErased(instance, handed, own) !== false));
|
|
2230
|
+
})));
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* `request: { param, path?, url?, method?, body? }` on a receive op (#2474
|
|
2234
|
+
* R0, carrick#2674): some signature of the handler this overload takes
|
|
2235
|
+
* declares a parameter at `param` on which every member path the claim
|
|
2236
|
+
* gives resolves, step by step, through properties the library declares
|
|
2237
|
+
* (`declaredProperty`). Undefined when it holds; the declarations of the
|
|
2238
|
+
* member the `body` path ends on are then added to `bodyMembers`.
|
|
1968
2239
|
*/
|
|
1969
|
-
requestOutcome(signature, handler, request) {
|
|
2240
|
+
requestOutcome(signature, handler, request, bodyMembers) {
|
|
1970
2241
|
let declared = this.keyParameterAt(signature, handler.arg);
|
|
1971
2242
|
if (declared !== undefined && handler.key !== undefined) {
|
|
1972
2243
|
const property = this.declaredProperty(declared, handler.key);
|
|
@@ -1977,25 +2248,33 @@ class DeclarationReader {
|
|
|
1977
2248
|
const slot = this.throughConditional(this.throughConstraint(declared));
|
|
1978
2249
|
if (slot === VARIADIC)
|
|
1979
2250
|
return unchecked('handler_untyped');
|
|
1980
|
-
const paths = [request.path, request.url, request.method].filter((p) => p !== undefined);
|
|
2251
|
+
const paths = [request.path, request.url, request.method, request.body].filter((p) => p !== undefined);
|
|
1981
2252
|
let best = failed('param_missing');
|
|
1982
2253
|
for (const part of this.parts(slot)) {
|
|
1983
2254
|
for (const handed of part.getCallSignatures()) {
|
|
1984
2255
|
const given = this.parameterAt(handed, request.param);
|
|
1985
2256
|
if (given === undefined || given === VARIADIC)
|
|
1986
2257
|
continue;
|
|
2258
|
+
const ends = new Map();
|
|
1987
2259
|
const resolves = paths.every(members => {
|
|
1988
2260
|
let at = given;
|
|
2261
|
+
let last;
|
|
1989
2262
|
for (const member of members) {
|
|
1990
2263
|
const property = this.declaredProperty(at, member);
|
|
1991
2264
|
if (!property)
|
|
1992
2265
|
return false;
|
|
2266
|
+
last = property;
|
|
1993
2267
|
at = this.checker.getTypeOfSymbol(property);
|
|
1994
2268
|
}
|
|
2269
|
+
if (last)
|
|
2270
|
+
ends.set(members, last);
|
|
1995
2271
|
return true;
|
|
1996
2272
|
});
|
|
1997
|
-
if (resolves)
|
|
2273
|
+
if (resolves) {
|
|
2274
|
+
const body = request.body ? ends.get(request.body) : undefined;
|
|
2275
|
+
bodyMembers.push(...(body?.declarations ?? []));
|
|
1998
2276
|
return undefined;
|
|
2277
|
+
}
|
|
1999
2278
|
best = failed('request_member_missing');
|
|
2000
2279
|
}
|
|
2001
2280
|
}
|
|
@@ -2060,8 +2339,12 @@ class DeclarationReader {
|
|
|
2060
2339
|
* prefix key accepting a string and a handler key a declared function.
|
|
2061
2340
|
* 4. Every string slot of the call besides the name is accounted for
|
|
2062
2341
|
* (design D2, `nameSiblings`).
|
|
2342
|
+
*
|
|
2343
|
+
* `runsAs` replaces the `runs` predicate (an adapter's collection, #2474
|
|
2344
|
+
* R6a); absent, a `runs` slot is a declared function.
|
|
2063
2345
|
*/
|
|
2064
|
-
messageSignatureOutcome(signature, layout, labels) {
|
|
2346
|
+
messageSignatureOutcome(signature, layout, labels, runsAs) {
|
|
2347
|
+
const runsFailure = runsAs ?? ((declared) => this.runsFailure(declared));
|
|
2065
2348
|
for (const [arg, part] of layout.positional) {
|
|
2066
2349
|
if (part === 'method') {
|
|
2067
2350
|
const declared = this.keyParameterAt(signature, arg);
|
|
@@ -2098,7 +2381,7 @@ class DeclarationReader {
|
|
|
2098
2381
|
if (part !== 'handler' && part !== 'ack' && part !== 'runs')
|
|
2099
2382
|
continue;
|
|
2100
2383
|
const declared = this.keyParameterAt(signature, arg);
|
|
2101
|
-
const failure = part === 'runs' ?
|
|
2384
|
+
const failure = part === 'runs' ? runsFailure(declared) : this.handlerFailure(declared);
|
|
2102
2385
|
if (failure)
|
|
2103
2386
|
return { rank: 3, outcome: failure };
|
|
2104
2387
|
}
|
|
@@ -2107,7 +2390,7 @@ class DeclarationReader {
|
|
|
2107
2390
|
const slot = this.keyParameterAt(signature, arg);
|
|
2108
2391
|
if (slot === undefined)
|
|
2109
2392
|
return { rank: 1, outcome: failed('param_missing') };
|
|
2110
|
-
const view = this.viewFor(slot, keys);
|
|
2393
|
+
const view = this.viewFor(slot, keys, runsFailure);
|
|
2111
2394
|
if ('rank' in view)
|
|
2112
2395
|
return view;
|
|
2113
2396
|
views.set(arg, view.view);
|
|
@@ -2122,7 +2405,7 @@ class DeclarationReader {
|
|
|
2122
2405
|
* union member that is an object type and not a function type, declaring
|
|
2123
2406
|
* every claimed key with the type its part needs.
|
|
2124
2407
|
*/
|
|
2125
|
-
viewFor(slot, keys) {
|
|
2408
|
+
viewFor(slot, keys, runsFailure) {
|
|
2126
2409
|
if (slot === VARIADIC || !this.isObjectType(slot)) {
|
|
2127
2410
|
return { rank: 1, outcome: this.slotFailure(slot, 'param_missing') };
|
|
2128
2411
|
}
|
|
@@ -2152,7 +2435,7 @@ class DeclarationReader {
|
|
|
2152
2435
|
break;
|
|
2153
2436
|
}
|
|
2154
2437
|
if (part === 'runs') {
|
|
2155
|
-
const bad =
|
|
2438
|
+
const bad = runsFailure(type);
|
|
2156
2439
|
if (bad) {
|
|
2157
2440
|
failure = { rank: 3, outcome: bad };
|
|
2158
2441
|
break;
|
|
@@ -2719,8 +3002,20 @@ class DeclarationReader {
|
|
|
2719
3002
|
dropped += 1;
|
|
2720
3003
|
return false;
|
|
2721
3004
|
};
|
|
2722
|
-
//
|
|
3005
|
+
// Handler-parameter members (carrick#2674) have a cap of their own, so they
|
|
3006
|
+
// take nothing from the main one: the listing, `truncated` and the hash are
|
|
3007
|
+
// the ones the lister gave before handlers were listed.
|
|
3008
|
+
let handlerBudget = maxEntries;
|
|
3009
|
+
const takeHandler = () => {
|
|
3010
|
+
if (handlerBudget <= 0)
|
|
3011
|
+
return false;
|
|
3012
|
+
handlerBudget -= 1;
|
|
3013
|
+
return true;
|
|
3014
|
+
};
|
|
3015
|
+
// Four passes: every export and receiver, then every member name, then the
|
|
3016
|
+
// members of each handler's parameters (carrick#2674), then signatures.
|
|
2723
3017
|
const names = [];
|
|
3018
|
+
const handlers = [];
|
|
2724
3019
|
const fills = [];
|
|
2725
3020
|
// What the package's default export names: a maker also exported by name
|
|
2726
3021
|
// under this declaration is imported the same way, so it is read the same.
|
|
@@ -2736,7 +3031,7 @@ class DeclarationReader {
|
|
|
2736
3031
|
if (!take())
|
|
2737
3032
|
return;
|
|
2738
3033
|
const home = this.homeOf(pkg, [target, ...typeSymbols(receiverType)]);
|
|
2739
|
-
entry.receivers.push(this.outlineReceiver(pkg, receiverName, receiverType, home, take, names, fills));
|
|
3034
|
+
entry.receivers.push(this.outlineReceiver(pkg, receiverName, receiverType, home, take, takeHandler, names, handlers, fills));
|
|
2740
3035
|
};
|
|
2741
3036
|
receiverOf('export', type);
|
|
2742
3037
|
for (const maker of this.makers(pkg, target, type)) {
|
|
@@ -2753,6 +3048,8 @@ class DeclarationReader {
|
|
|
2753
3048
|
}
|
|
2754
3049
|
for (const run of names)
|
|
2755
3050
|
run();
|
|
3051
|
+
for (const run of handlers)
|
|
3052
|
+
run();
|
|
2756
3053
|
for (const fill of fills)
|
|
2757
3054
|
fill();
|
|
2758
3055
|
surface.truncated = dropped;
|
|
@@ -2897,10 +3194,11 @@ class DeclarationReader {
|
|
|
2897
3194
|
}
|
|
2898
3195
|
/**
|
|
2899
3196
|
* A receiver, now; its member names later (`names`), once every export and
|
|
2900
|
-
* receiver of the package is listed;
|
|
2901
|
-
*
|
|
3197
|
+
* receiver of the package is listed; the members of its handlers'
|
|
3198
|
+
* parameters next (`handlers`), once every name is; its call, construct and
|
|
3199
|
+
* member signatures last (`fills`), once every handler is.
|
|
2902
3200
|
*/
|
|
2903
|
-
outlineReceiver(pkg, receiverName, type, home, take, names, fills) {
|
|
3201
|
+
outlineReceiver(pkg, receiverName, type, home, take, takeHandler, names, handlers, fills) {
|
|
2904
3202
|
const receiver = { receiver: receiverName, members: [] };
|
|
2905
3203
|
const holder = this.surfaceHolder(pkg, type, home);
|
|
2906
3204
|
// The receiver's own call and construct signatures, as a claim with a
|
|
@@ -2927,11 +3225,102 @@ class DeclarationReader {
|
|
|
2927
3225
|
const own = 'signatures' in callee;
|
|
2928
3226
|
const member = { name: property.getName(), own, signatures: [] };
|
|
2929
3227
|
receiver.members.push(member);
|
|
3228
|
+
handlers.push(() => {
|
|
3229
|
+
const listed = this.listHandlers(own ? callee.signatures : library, takeHandler);
|
|
3230
|
+
if (listed.length > 0)
|
|
3231
|
+
member.handlers = listed;
|
|
3232
|
+
});
|
|
2930
3233
|
fills.push(() => (member.signatures = this.listSignatures(own ? callee.signatures : library, holder.type, take)));
|
|
2931
3234
|
}
|
|
2932
3235
|
});
|
|
2933
3236
|
return receiver;
|
|
2934
3237
|
}
|
|
3238
|
+
/**
|
|
3239
|
+
* The functions a member's signatures take, at a positional argument or at a
|
|
3240
|
+
* key of an options object, with the members their parameters declare. A
|
|
3241
|
+
* handler is listed once per place, from the first signature that states it.
|
|
3242
|
+
* A parameter that declares no member lists nothing, and so does a handler
|
|
3243
|
+
* none of whose parameters do.
|
|
3244
|
+
*/
|
|
3245
|
+
listHandlers(signatures, take) {
|
|
3246
|
+
const listed = [];
|
|
3247
|
+
const seen = new Set();
|
|
3248
|
+
const add = (arg, key, slot) => {
|
|
3249
|
+
const place = JSON.stringify([arg, key ?? null]);
|
|
3250
|
+
if (seen.has(place))
|
|
3251
|
+
return;
|
|
3252
|
+
const params = this.listHandlerParams(slot, take);
|
|
3253
|
+
if (params.length === 0)
|
|
3254
|
+
return;
|
|
3255
|
+
seen.add(place);
|
|
3256
|
+
listed.push(key === undefined ? { arg, params } : { arg, key, params });
|
|
3257
|
+
};
|
|
3258
|
+
for (const signature of signatures) {
|
|
3259
|
+
const count = signature.getParameters().length;
|
|
3260
|
+
for (let arg = 0; arg < count; arg++) {
|
|
3261
|
+
const slot = this.keyParameterAt(signature, arg);
|
|
3262
|
+
if (slot === undefined || slot === VARIADIC)
|
|
3263
|
+
continue;
|
|
3264
|
+
if (this.handlerFailure(slot) === undefined) {
|
|
3265
|
+
add(arg, undefined, slot);
|
|
3266
|
+
continue;
|
|
3267
|
+
}
|
|
3268
|
+
if (!this.isObjectType(slot))
|
|
3269
|
+
continue;
|
|
3270
|
+
const keys = new Map();
|
|
3271
|
+
for (const view of this.keyViews(slot)) {
|
|
3272
|
+
for (const property of this.namedProperties(view)) {
|
|
3273
|
+
const type = this.checker.getTypeOfSymbol(property);
|
|
3274
|
+
if (!keys.has(property.getName()) && this.handlerFailure(type) === undefined)
|
|
3275
|
+
keys.set(property.getName(), type);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
for (const key of [...keys.keys()].sort(byName))
|
|
3279
|
+
add(arg, key, keys.get(key));
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return listed;
|
|
3283
|
+
}
|
|
3284
|
+
/** The parameters of a function type's first signature that declare members, each member counting against the cap. */
|
|
3285
|
+
listHandlerParams(handler, take) {
|
|
3286
|
+
const slot = this.throughConditional(this.throughConstraint(handler));
|
|
3287
|
+
if (slot === VARIADIC)
|
|
3288
|
+
return [];
|
|
3289
|
+
const signature = this.parts(slot)
|
|
3290
|
+
.flatMap(part => part.getCallSignatures())
|
|
3291
|
+
.find(each => each.getParameters().length > 0);
|
|
3292
|
+
if (!signature)
|
|
3293
|
+
return [];
|
|
3294
|
+
const listed = [];
|
|
3295
|
+
signature.getParameters().forEach((parameter, index) => {
|
|
3296
|
+
const type = this.checker.getTypeOfSymbol(parameter);
|
|
3297
|
+
if (this.namedProperties(type).length === 0 || !take())
|
|
3298
|
+
return;
|
|
3299
|
+
const members = [];
|
|
3300
|
+
this.listMembers(type, [], members, take);
|
|
3301
|
+
listed.push({ param: index, name: parameter.getName(), type: this.printed(type), members });
|
|
3302
|
+
});
|
|
3303
|
+
return listed;
|
|
3304
|
+
}
|
|
3305
|
+
/** A type's declared members by path, parents before children, each level sorted by name, down to `HANDLER_MEMBER_DEPTH`. */
|
|
3306
|
+
listMembers(type, prefix, into, take) {
|
|
3307
|
+
for (const property of sortedByName(this.namedProperties(type))) {
|
|
3308
|
+
if (!take())
|
|
3309
|
+
continue;
|
|
3310
|
+
const memberType = this.checker.getTypeOfSymbol(property);
|
|
3311
|
+
const path = [...prefix, property.getName()];
|
|
3312
|
+
const callable = this.librarySignatures(memberType, 'call').length > 0;
|
|
3313
|
+
into.push({
|
|
3314
|
+
path,
|
|
3315
|
+
type: this.printed(memberType),
|
|
3316
|
+
optional: (property.flags & ts.SymbolFlags.Optional) !== 0,
|
|
3317
|
+
function: callable,
|
|
3318
|
+
});
|
|
3319
|
+
if (!callable && path.length < HANDLER_MEMBER_DEPTH && this.isObjectType(memberType)) {
|
|
3320
|
+
this.listMembers(memberType, path, into, take);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
2935
3324
|
listSignatures(signatures, holder, take) {
|
|
2936
3325
|
const listed = [];
|
|
2937
3326
|
for (const signature of signatures) {
|
|
@@ -3482,6 +3871,8 @@ class DeclarationReader {
|
|
|
3482
3871
|
* union the builder cuts short keeps the members it made first, so the
|
|
3483
3872
|
* listing orders every union before its own cut (`truncate`).
|
|
3484
3873
|
*/
|
|
3874
|
+
/** The longest member path a handler parameter's members are listed to (`['raw', 'url']`). */
|
|
3875
|
+
const HANDLER_MEMBER_DEPTH = 2;
|
|
3485
3876
|
const PRINT_FLAGS = ts.NodeBuilderFlags.AllowUniqueESSymbolType |
|
|
3486
3877
|
ts.NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope |
|
|
3487
3878
|
ts.NodeBuilderFlags.IgnoreErrors |
|