carrick 0.3.133 → 0.3.135

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.
Files changed (70) hide show
  1. package/dist/contract.d.ts +6 -0
  2. package/dist/contract.js.map +1 -1
  3. package/dist/hook/post-edit.d.ts +10 -5
  4. package/dist/hook/post-edit.js +80 -23
  5. package/dist/hook/post-edit.js.map +1 -1
  6. package/dist/hook/refresh.d.ts +23 -3
  7. package/dist/hook/refresh.js +48 -6
  8. package/dist/hook/refresh.js.map +1 -1
  9. package/dist/hook/reuse.d.ts +7 -21
  10. package/dist/hook/reuse.js +23 -36
  11. package/dist/hook/reuse.js.map +1 -1
  12. package/dist/hook/session-start.js +15 -2
  13. package/dist/hook/session-start.js.map +1 -1
  14. package/dist/hook/stop.js.map +1 -1
  15. package/dist/init/agents-section.js +1 -1
  16. package/dist/init/agents-section.js.map +1 -1
  17. package/dist/init/doctor.js +4 -0
  18. package/dist/init/doctor.js.map +1 -1
  19. package/dist/init/hosted.d.ts +1 -0
  20. package/dist/init/hosted.js +4 -1
  21. package/dist/init/hosted.js.map +1 -1
  22. package/dist/init/remove.js +7 -1
  23. package/dist/init/remove.js.map +1 -1
  24. package/dist/init/repo-copies.js +3 -1
  25. package/dist/init/repo-copies.js.map +1 -1
  26. package/dist/init/run.d.ts +1 -1
  27. package/dist/init/run.js +10 -2
  28. package/dist/init/run.js.map +1 -1
  29. package/dist/init/task-skills.d.ts +22 -1
  30. package/dist/init/task-skills.js +65 -1
  31. package/dist/init/task-skills.js.map +1 -1
  32. package/dist/render.d.ts +41 -16
  33. package/dist/render.js +91 -102
  34. package/dist/render.js.map +1 -1
  35. package/package.json +10 -9
  36. package/plugin/.claude-plugin/plugin.json +1 -1
  37. package/sidecar/dist/src/capture/anchors.js +18 -1
  38. package/sidecar/dist/src/capture/api.d.ts +17 -4
  39. package/sidecar/dist/src/capture/check-workspace.js +1 -1
  40. package/sidecar/dist/src/capture/check.d.ts +44 -0
  41. package/sidecar/dist/src/capture/check.js +112 -10
  42. package/sidecar/dist/src/capture/index.js +18 -0
  43. package/sidecar/dist/src/capture/installed-package.d.ts +5 -0
  44. package/sidecar/dist/src/capture/installed-package.js +64 -6
  45. package/sidecar/dist/src/capture/machine-paths.d.ts +44 -0
  46. package/sidecar/dist/src/capture/machine-paths.js +97 -0
  47. package/sidecar/dist/src/capture/node-builder.d.ts +2 -0
  48. package/sidecar/dist/src/capture/node-builder.js +1 -1
  49. package/sidecar/dist/src/capture/paths-rewrite.js +3 -3
  50. package/sidecar/dist/src/capture/result-label.d.ts +42 -0
  51. package/sidecar/dist/src/capture/result-label.js +91 -0
  52. package/sidecar/dist/src/capture/self-check.js +13 -2
  53. package/sidecar/dist/src/index.js +117 -5
  54. package/sidecar/dist/src/infer-cache.d.ts +125 -0
  55. package/sidecar/dist/src/infer-cache.js +244 -0
  56. package/sidecar/dist/src/library-claims.d.ts +35 -3
  57. package/sidecar/dist/src/library-claims.js +417 -61
  58. package/sidecar/dist/src/node-index.d.ts +53 -0
  59. package/sidecar/dist/src/node-index.js +100 -0
  60. package/sidecar/dist/src/project-loader.d.ts +11 -0
  61. package/sidecar/dist/src/project-loader.js +15 -0
  62. package/sidecar/dist/src/result-candidates.d.ts +28 -0
  63. package/sidecar/dist/src/result-candidates.js +209 -0
  64. package/sidecar/dist/src/type-inferrer.d.ts +27 -0
  65. package/sidecar/dist/src/type-inferrer.js +175 -43
  66. package/sidecar/dist/src/types.d.ts +77 -1
  67. package/sidecar/dist/src/unwidened.js +10 -1
  68. package/sidecar/dist/src/validators.d.ts +931 -197
  69. package/sidecar/dist/src/validators.js +34 -0
  70. package/templates/skills/carrick-reuse.md +11 -0
@@ -39,6 +39,7 @@ import * as crypto from 'node:crypto';
39
39
  import * as fs from 'node:fs';
40
40
  import * as path from 'node:path';
41
41
  import { ts } from 'ts-morph';
42
+ import { holdsResult, resultCandidatesOf } from './result-candidates.js';
42
43
  import { PathNames, declaringFileName } from './position-rows.js';
43
44
  import { memberDeclaration } from './declaration-attribution.js';
44
45
  /** The methods a `verb` claim may name, and a method key may accept. */
@@ -258,6 +259,12 @@ function sortedDeclarations(declarations) {
258
259
  }
259
260
  let probeSequence = 0;
260
261
  const NO_MODULE_GRAPH = { packageDirectoryOf: () => undefined, resolveModule: () => undefined };
262
+ /**
263
+ * Test instrumentation, never on the wire: how many times the lister built a
264
+ * handler parameter's members and printed a type, since the caller last
265
+ * reset them (carrick#2977).
266
+ */
267
+ export const surfaceReadCounts = { handlerMemberBuilds: 0, typePrints: 0 };
261
268
  export class LibraryClaimsVerifier {
262
269
  project;
263
270
  graph;
@@ -280,6 +287,7 @@ export class LibraryClaimsVerifier {
280
287
  receiver: check.receiver,
281
288
  verdict: 'verified',
282
289
  ...(outcome.declarations?.length ? { declarations: outcome.declarations } : {}),
290
+ ...(outcome.requestDropped === undefined ? {} : { request_dropped: outcome.requestDropped }),
283
291
  }
284
292
  : {
285
293
  claim_id: check.claim_id,
@@ -586,6 +594,8 @@ export class LibraryClaimsVerifier {
586
594
  const declarations = [];
587
595
  const members = [];
588
596
  const bodyMembers = [];
597
+ // A request one receiver does not hold is dropped for the op (carrick#2968).
598
+ let requestDropped;
589
599
  for (const receiver of receivers.value) {
590
600
  const result = reader.judgeMessage(receiver, check.claim, rules, builtAtDefaults, collectionsOf);
591
601
  if (result.outcome.verdict !== 'verified')
@@ -594,11 +604,14 @@ export class LibraryClaimsVerifier {
594
604
  declarations.push(...(result.outcome.declarations ?? []));
595
605
  members.push(...(result.outcome.members ?? []));
596
606
  bodyMembers.push(...(result.outcome.bodyMembers ?? []));
607
+ requestDropped ??= result.outcome.requestDropped;
597
608
  }
598
609
  const verified = declarations.length > 0 ? { verdict: 'verified', declarations: sortedDeclarations(declarations) } : { ...VERIFIED };
599
610
  if (verified.verdict === 'verified' && members.length > 0)
600
611
  verified.members = members;
601
- if (verified.verdict === 'verified' && bodyMembers.length > 0)
612
+ if (verified.verdict === 'verified' && requestDropped !== undefined)
613
+ verified.requestDropped = requestDropped;
614
+ else if (verified.verdict === 'verified' && bodyMembers.length > 0)
602
615
  verified.bodyMembers = bodyMembers;
603
616
  return { outcome: verified, holding };
604
617
  };
@@ -644,7 +657,11 @@ export class LibraryClaimsVerifier {
644
657
  const observe = (check, outcome) => {
645
658
  if (check.claim.kind !== 'op')
646
659
  return;
647
- const read = outcome.verdict === 'verified' ? 'verified' : `${outcome.verdict} ${outcome.reason}`;
660
+ const read = outcome.verdict !== 'verified'
661
+ ? `${outcome.verdict} ${outcome.reason}`
662
+ : outcome.requestDropped === undefined
663
+ ? 'verified'
664
+ : `verified request_dropped ${outcome.requestDropped}`;
648
665
  outcomes[read] = (outcomes[read] ?? 0) + 1;
649
666
  if (outcome.verdict !== 'verified')
650
667
  return;
@@ -677,9 +694,20 @@ export class LibraryClaimsVerifier {
677
694
  * (`receiverExpression` on the export's `importLine`, read through
678
695
  * `DeclarationReader.returnOf`) and lists from it.
679
696
  *
697
+ * A member of an `instance:` receiver whose call returns another object
698
+ * that routes are registered on lists that object as its `scope_receiver`
699
+ * (carrick#2774), built as the verifier builds a scope step, under a cap of
700
+ * its own and left out of the hash.
701
+ *
702
+ * Each member's handler-parameter members (carrick#2674 S3) are listed
703
+ * only when `options.handlers` is set, and each signature's result
704
+ * candidates (carrick#2817) only when `options.resultCandidates` is
705
+ * (carrick#2966): on a large package either costs more than the rest of the
706
+ * listing.
707
+ *
680
708
  * `surface_sha256` is the full-surface hash (see `fullSurfaceSha256`).
681
709
  */
682
- listSurface(fromDir, packages, maxEntries, only = {}) {
710
+ listSurface(fromDir, packages, maxEntries, only = {}, options = {}) {
683
711
  if (packages.length === 0)
684
712
  return { surfaces: [], surface_sha256: fullSurfaceSha256([]) };
685
713
  // Each specifier twice: as a namespace, which holds its named exports, and
@@ -708,10 +736,12 @@ export class LibraryClaimsVerifier {
708
736
  const buildLines = [];
709
737
  const builtIndex = new Map();
710
738
  packages.forEach((pkg, i) => {
711
- for (const { export: name, receivers } of makers[i]) {
739
+ for (const { export: name, receivers, scopes } of makers[i]) {
712
740
  const local = `__carrick_e${importLines.length}`;
713
741
  importLines.push(probeLine(pkg, name, local));
714
- for (const receiver of receivers) {
742
+ // The scope receivers a maker's members may return (carrick#2774) are
743
+ // built beside the makers, so `listPackage` reads them as it reads those.
744
+ for (const receiver of [...receivers, ...scopes]) {
715
745
  const expression = receiverExpression(local, receiver);
716
746
  if (expression === undefined)
717
747
  continue;
@@ -721,7 +751,7 @@ export class LibraryClaimsVerifier {
721
751
  }
722
752
  });
723
753
  const firstBuild = imports.length + importLines.length;
724
- 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) => {
754
+ const surfaces = this.readProbe(fromDir, [...imports, ...importLines, ...buildLines], (checker, reader, file) => packages.map((pkg, i) => reader.listPackage(pkg, probeOf(file, i), maxEntries, only[pkg], options, (name, receiver) => {
725
755
  const index = builtIndex.get(builtKey({ package: pkg, export: name }, receiver));
726
756
  return index === undefined ? undefined : resolvedBuild(checker, file.statements[firstBuild + index]);
727
757
  })));
@@ -762,8 +792,8 @@ function resolvedBuild(checker, statement) {
762
792
  * array of `[package, exports]` for every surface that listed at least one
763
793
  * export, sorted by `package` (code unit order). A specifier that lists
764
794
  * nothing (unresolved, local, no value exports) is left out, so asking for a
765
- * subpath a version does not have changes nothing. Handler members are not
766
- * hashed. Type text already names
795
+ * subpath a version does not have changes nothing. Handler members, result
796
+ * candidates and scope receivers are not hashed. Type text already names
767
797
  * the request's directory as `<root>`.
768
798
  */
769
799
  export function fullSurfaceSha256(surfaces) {
@@ -771,9 +801,11 @@ export function fullSurfaceSha256(surfaces) {
771
801
  .filter(surface => surface.reason === undefined && surface.exports.length > 0)
772
802
  .map(surface => [surface.package, surface.exports])
773
803
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
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));
804
+ // A member's `handlers` (carrick#2674), a signature's `result_candidates`
805
+ // (carrick#2817) and a member's `scope_receiver` (carrick#2774) are out of
806
+ // the hash at every depth, so a package's key does not change when the
807
+ // lister learns to list them.
808
+ const text = JSON.stringify(listed, (key, value) => key === 'handlers' || key === 'result_candidates' || key === 'scope_receiver' ? undefined : value);
777
809
  return crypto.createHash('sha256').update(text).digest('hex');
778
810
  }
779
811
  function exportKey(check) {
@@ -926,6 +958,18 @@ function receiverExpression(local, receiver) {
926
958
  text = `${text}${parsed.scope.split('.').map(access).join('')}()`;
927
959
  return text;
928
960
  }
961
+ /**
962
+ * The id of the receiver `member` of the receiver `receiver` returns when
963
+ * called with a path (carrick#2774): `receiver>scope:member`. Undefined for a
964
+ * receiver that is not an instance, or a member name the verifier's grammar
965
+ * does not read back as one scope step.
966
+ */
967
+ function scopeReceiverId(receiver, member) {
968
+ if (!receiver.startsWith('instance:') || receiver.includes('>') || /[>.\s]/.test(member))
969
+ return undefined;
970
+ const id = `${receiver}>scope:${member}`;
971
+ return parseReceiver(id)?.scope === member ? id : undefined;
972
+ }
929
973
  /** The symbols that say who declared a type: its own, its alias's, its parts'. */
930
974
  function typeSymbols(type) {
931
975
  const symbols = [type.getSymbol(), type.aliasSymbol];
@@ -1018,8 +1062,24 @@ class DeclarationReader {
1018
1062
  currentPackage = '';
1019
1063
  /** `declaredProperties` per type, for `currentPackage` (its augmentations decide them). */
1020
1064
  declared = new Map();
1065
+ /**
1066
+ * A handler parameter's listed members per type (`handlerMembersOf`), kept
1067
+ * across every specifier of one package and cleared with `declared` when
1068
+ * the package changes: a member's listing reads that package's
1069
+ * augmentations (carrick#2977).
1070
+ */
1071
+ handlerMembers = new Map();
1072
+ /** `takesString` per signature, and `scopeMembers` per type and home: the same types recur across receivers. */
1073
+ takesStringRead = new Map();
1074
+ scopeMembersRead = new Map();
1075
+ /** Handler-parameter members `HANDLER_MEMBER_CAP` dropped in the package being listed. */
1076
+ handlersDropped = 0;
1077
+ /** Whether the package being listed lists each signature's `result_candidates`. */
1078
+ listResultCandidates = false;
1021
1079
  /** The service root as given and as a realpath, longest first: what a listing prints as `<root>`. */
1022
1080
  printedRoots;
1081
+ /** `printed` per type: its text reads nothing but the type and `printedRoots` (carrick#2977). */
1082
+ printedTexts = new Map();
1023
1083
  constructor(program, probe, host, serviceRoot, scanRoot, graph) {
1024
1084
  this.program = program;
1025
1085
  this.probe = probe;
@@ -1365,6 +1425,8 @@ class DeclarationReader {
1365
1425
  * `claim_invalid`.
1366
1426
  */
1367
1427
  judgeHttp(receiver, claim) {
1428
+ if (claim.kind === 'result')
1429
+ return this.judgeHttpResult(receiver, claim);
1368
1430
  if (claim.kind === 'make') {
1369
1431
  const base = claim.base;
1370
1432
  if (claim.form !== 'call' ||
@@ -1624,7 +1686,37 @@ class DeclarationReader {
1624
1686
  return { outcome: this.judgeOp(receiver, claim, rules) };
1625
1687
  case 'reserved':
1626
1688
  return { outcome: this.judgeReserved(receiver, claim) };
1689
+ case 'result':
1690
+ return { outcome: this.resultOutcome(this.ownCallee(receiver, claim.path ?? [], claim.member, 'call'), claim.result) };
1691
+ }
1692
+ }
1693
+ /**
1694
+ * A `result` claim (carrick#2817): the claimed result is a candidate of at
1695
+ * least one signature the callee declares, by rule R (`result-candidates.ts`).
1696
+ * Anything else, including a call that returns nothing, is
1697
+ * `result_not_candidate`.
1698
+ */
1699
+ resultOutcome(callee, shape) {
1700
+ if ('failure' in callee)
1701
+ return callee.failure;
1702
+ for (const signature of callee.signatures) {
1703
+ const candidates = resultCandidatesOf(this.checker, signature);
1704
+ if (candidates !== undefined && holdsResult(candidates, shape))
1705
+ return VERIFIED;
1627
1706
  }
1707
+ return failed('result_not_candidate');
1708
+ }
1709
+ /** A `result` claim on an HTTP receiver: `path` walked from it, then `member` of what that reaches. */
1710
+ judgeHttpResult(receiver, claim) {
1711
+ let holder = receiver;
1712
+ for (const hop of claim.path ?? []) {
1713
+ const property = this.declaredProperty(holder, hop);
1714
+ if (!property)
1715
+ return failed('member_missing');
1716
+ holder = this.checker.getTypeOfSymbol(property);
1717
+ }
1718
+ const callee = claim.member === null ? this.callSignatures(holder) : this.callableProperty(holder, claim.member);
1719
+ return this.resultOutcome(callee, claim.result);
1628
1720
  }
1629
1721
  /**
1630
1722
  * `member` (or the export itself, when null) is callable (`call`) or
@@ -2011,6 +2103,10 @@ class DeclarationReader {
2011
2103
  const labels = claim.key_labels ?? {};
2012
2104
  let best;
2013
2105
  let held = false;
2106
+ // An overload that holds but for a request member the declarations lack
2107
+ // (carrick#2968): the op stands without its request, unless another
2108
+ // overload holds the request too. Every other request fault fails the op.
2109
+ let requestDropped;
2014
2110
  const bodyMembers = [];
2015
2111
  for (const signature of callee.signatures) {
2016
2112
  let result = this.messageSignatureOutcome(signature, layout.value, labels);
@@ -2021,19 +2117,26 @@ class DeclarationReader {
2021
2117
  }
2022
2118
  if (result.rank === Infinity && claim.request !== undefined) {
2023
2119
  const read = this.requestOutcome(signature, claim.handler, claim.request, bodyMembers);
2120
+ if (read?.verdict === 'failed' && read.reason === 'request_member_missing') {
2121
+ requestDropped = read.reason;
2122
+ continue;
2123
+ }
2024
2124
  if (read)
2025
2125
  result = { rank: 5, outcome: read };
2026
2126
  }
2027
2127
  if (result.rank === Infinity) {
2028
2128
  held = true;
2129
+ requestDropped = undefined;
2029
2130
  break;
2030
2131
  }
2031
2132
  best = furthest(best, result.rank, result.outcome);
2032
2133
  }
2033
- if (!held)
2134
+ if (!held && requestDropped === undefined)
2034
2135
  return best.outcome;
2035
- if (boundRuns)
2036
- return this.boundRunsOutcome(receiver.makerHolding, boundRuns);
2136
+ if (boundRuns) {
2137
+ const ran = this.boundRunsOutcome(receiver.makerHolding, boundRuns);
2138
+ return ran.verdict === 'verified' && requestDropped !== undefined ? { ...ran, requestDropped } : ran;
2139
+ }
2037
2140
  const declarations = this.declarationsOf(callee);
2038
2141
  const members = callee.named
2039
2142
  ? (callee.named.property.declarations ?? [])
@@ -2043,6 +2146,7 @@ class DeclarationReader {
2043
2146
  ...(declarations.length > 0 ? { declarations } : {}),
2044
2147
  ...(members.length > 0 ? { members } : {}),
2045
2148
  ...(bodyMembers.length > 0 ? { bodyMembers } : {}),
2149
+ ...(requestDropped === undefined ? {} : { requestDropped }),
2046
2150
  };
2047
2151
  }
2048
2152
  /**
@@ -2977,7 +3081,7 @@ class DeclarationReader {
2977
3081
  * `built` answers the probe's no-argument build of a receiver an export
2978
3082
  * makes, resolved, when the probe holds one.
2979
3083
  */
2980
- listPackage(pkg, probe, maxEntries, only, built) {
3084
+ listPackage(pkg, probe, maxEntries, only, options, built) {
2981
3085
  this.setPackage(pkg);
2982
3086
  // Runtime modules on: a `node:` specifier is listed from the runtime's
2983
3087
  // types package, as the message roles read it.
@@ -3002,24 +3106,24 @@ class DeclarationReader {
3002
3106
  dropped += 1;
3003
3107
  return false;
3004
3108
  };
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
- };
3109
+ // Handler-parameter members (carrick#2674) take nothing from the main cap,
3110
+ // so the listing, `truncated` and the hash are the ones the lister gave
3111
+ // before handlers were listed; each parameter has a cap of its own
3112
+ // (`HANDLER_MEMBER_CAP`, carrick#2966).
3113
+ this.handlersDropped = 0;
3114
+ this.listResultCandidates = options.resultCandidates === true;
3115
+ const withHandlers = options.handlers === true;
3015
3116
  // Four passes: every export and receiver, then every member name, then the
3016
- // members of each handler's parameters (carrick#2674), then signatures.
3117
+ // members of each handler's parameters (carrick#2674; only when asked,
3118
+ // carrick#2966), then signatures.
3017
3119
  const names = [];
3018
3120
  const handlers = [];
3019
3121
  const fills = [];
3020
3122
  // What the package's default export names: a maker also exported by name
3021
3123
  // under this declaration is imported the same way, so it is read the same.
3022
3124
  const defaultTarget = exported.find(each => each.name === 'default')?.target;
3125
+ // The instance receivers listed, for the scope pass (carrick#2774).
3126
+ const instances = [];
3023
3127
  for (const { name, target, type } of exported) {
3024
3128
  if (!take())
3025
3129
  continue;
@@ -3031,7 +3135,11 @@ class DeclarationReader {
3031
3135
  if (!take())
3032
3136
  return;
3033
3137
  const home = this.homeOf(pkg, [target, ...typeSymbols(receiverType)]);
3034
- entry.receivers.push(this.outlineReceiver(pkg, receiverName, receiverType, home, take, takeHandler, names, handlers, fills));
3138
+ const receiver = this.outlineReceiver(pkg, receiverName, receiverType, home, take, names, handlers, fills);
3139
+ entry.receivers.push(receiver);
3140
+ if (receiverName.startsWith('instance:')) {
3141
+ instances.push({ export: name, target, type: receiverType, home, receiver });
3142
+ }
3035
3143
  };
3036
3144
  receiverOf('export', type);
3037
3145
  for (const maker of this.makers(pkg, target, type)) {
@@ -3048,13 +3156,57 @@ class DeclarationReader {
3048
3156
  }
3049
3157
  for (const run of names)
3050
3158
  run();
3051
- for (const run of handlers)
3052
- run();
3159
+ if (withHandlers)
3160
+ for (const run of handlers)
3161
+ run();
3053
3162
  for (const fill of fills)
3054
3163
  fill();
3164
+ this.listScopeReceivers(pkg, instances, maxEntries, withHandlers, built);
3055
3165
  surface.truncated = dropped;
3166
+ if (withHandlers)
3167
+ surface.handlers_truncated = this.handlersDropped;
3056
3168
  return surface;
3057
3169
  }
3170
+ /**
3171
+ * The fifth pass (carrick#2774): each own member of an instance receiver
3172
+ * whose call returns the object routes are registered on lists it as
3173
+ * `scope_receiver`, outlined as any receiver is. Everything it lists draws
3174
+ * on one budget of its own, spent after every other pass, so the listing
3175
+ * without it, `truncated` and the hash are the ones the lister gave before.
3176
+ * One level: a scope receiver lists no scope receiver of its own.
3177
+ */
3178
+ listScopeReceivers(pkg, sources, maxEntries, withHandlers, built) {
3179
+ let budget = maxEntries;
3180
+ const take = () => budget > 0 && (budget--, true);
3181
+ const names = [];
3182
+ const handlers = [];
3183
+ const fills = [];
3184
+ for (const source of sources) {
3185
+ const holder = this.surfaceHolder(pkg, source.type, source.home);
3186
+ for (const member of source.receiver.members) {
3187
+ const id = member.own ? scopeReceiverId(source.receiver.receiver, member.name) : undefined;
3188
+ if (id === undefined)
3189
+ continue;
3190
+ const callee = this.ownCallee(holder, [], member.name, 'call');
3191
+ if (!('signatures' in callee))
3192
+ continue;
3193
+ if (!callee.signatures.some(signature => this.takesString(signature)))
3194
+ continue;
3195
+ const type = this.scopeTypeOf(callee.signatures, built(source.export, id), source.type, source.home);
3196
+ if (type === undefined || !take())
3197
+ continue;
3198
+ const home = this.homeOf(pkg, [source.target, ...typeSymbols(type)]);
3199
+ member.scope_receiver = this.outlineReceiver(pkg, id, type, home, take, names, handlers, fills);
3200
+ }
3201
+ }
3202
+ for (const run of names)
3203
+ run();
3204
+ if (withHandlers)
3205
+ for (const run of handlers)
3206
+ run();
3207
+ for (const fill of fills)
3208
+ fill();
3209
+ }
3058
3210
  /**
3059
3211
  * The receivers each export of a specifier can make, by receiver id, for
3060
3212
  * the probe statements that build them (see `LibraryClaimsVerifier.listSurface`).
@@ -3066,12 +3218,157 @@ class DeclarationReader {
3066
3218
  return [];
3067
3219
  return (this.listedExports(probe, only, moduleRead?.globalFile) ?? [])
3068
3220
  .filter(({ type }) => !this.isOpenTop(type))
3069
- .map(({ name, target, type }) => ({
3070
- export: name,
3071
- receivers: this.makers(pkg, target, type).map(maker => maker.receiver),
3072
- }))
3221
+ .map(({ name, target, type }) => {
3222
+ const makers = this.makers(pkg, target, type);
3223
+ return {
3224
+ export: name,
3225
+ receivers: makers.map(maker => maker.receiver),
3226
+ scopes: this.scopeCandidates(pkg, target, makers),
3227
+ };
3228
+ })
3073
3229
  .filter(({ receivers }) => receivers.length > 0);
3074
3230
  }
3231
+ /**
3232
+ * The `R>scope:m` receivers an export's `instance:` makers may have
3233
+ * (carrick#2774): for each object type a maker's overloads declare as their
3234
+ * return, each own member that `scopeTypeOf` lists at its declared return.
3235
+ * A superset of the listing, which reads each at the type the probe's build
3236
+ * resolves; the second program builds these so `listPackage` can.
3237
+ */
3238
+ scopeCandidates(pkg, target, makers) {
3239
+ const found = new Set();
3240
+ for (const maker of makers) {
3241
+ if (!maker.receiver.startsWith('instance:'))
3242
+ continue;
3243
+ const types = new Set(maker.signatures.map(sig => this.checker.getReturnTypeOfSignature(sig)));
3244
+ for (const type of types) {
3245
+ if (this.returnSaysNothing(type) || !this.isObjectType(type))
3246
+ continue;
3247
+ const home = this.homeOf(pkg, [target, ...typeSymbols(type)]);
3248
+ for (const name of this.scopeMembers(pkg, type, home)) {
3249
+ const id = scopeReceiverId(maker.receiver, name);
3250
+ if (id !== undefined)
3251
+ found.add(id);
3252
+ }
3253
+ }
3254
+ }
3255
+ return [...found];
3256
+ }
3257
+ /** The members of `type` that list a scope receiver at their declared return, read once per type and home. */
3258
+ scopeMembers(pkg, type, home) {
3259
+ const key = [...home].sort().join('\u0000');
3260
+ let byHome = this.scopeMembersRead.get(type);
3261
+ if (byHome === undefined)
3262
+ this.scopeMembersRead.set(type, (byHome = new Map()));
3263
+ const read = byHome.get(key);
3264
+ if (read !== undefined)
3265
+ return read;
3266
+ const names = [];
3267
+ const holder = this.surfaceHolder(pkg, type, home);
3268
+ for (const property of sortedByName(this.namedProperties(type))) {
3269
+ if (!this.mayScope(property))
3270
+ continue;
3271
+ const callee = this.ownCallee(holder, [], property.getName(), 'call');
3272
+ if ('signatures' in callee && this.scopeTypeOf(callee.signatures, undefined, type, home) !== undefined) {
3273
+ names.push(property.getName());
3274
+ }
3275
+ }
3276
+ byHome.set(key, names);
3277
+ return names;
3278
+ }
3279
+ /** Some library signature of the member takes a string: the cheap superset of what `scopeTypeOf` asks first. */
3280
+ mayScope(property) {
3281
+ return this.librarySignatures(this.checker.getTypeOfSymbol(property), 'call').some(signature => this.takesString(signature));
3282
+ }
3283
+ /**
3284
+ * The object a scope member returns, as the verifier reads a scope step
3285
+ * (carrick#2774): what a route is registered on when the member is called
3286
+ * with a path. Undefined unless:
3287
+ * - some own signature takes a string, positionally or at a key of an
3288
+ * object parameter;
3289
+ * - the returns (at `built`'s defaults, less those that say nothing) are
3290
+ * all object types, none thenable, sharing one declared symbol that is
3291
+ * declared in the receiver's home and has a callable member of its own;
3292
+ * - that symbol and the receiver's share no declaration in the home
3293
+ * (`family`): the receiver itself (`basePath(): this`), a sibling or a
3294
+ * wrapper of one base is a builder's next link, not an object routes
3295
+ * are registered on.
3296
+ * The type is the return of the overload `built` resolves, when that is
3297
+ * one of the signatures that take a string, else the first of them.
3298
+ */
3299
+ scopeTypeOf(signatures, built, receiver, home) {
3300
+ const takers = signatures.filter(signature => this.takesString(signature));
3301
+ const returns = takers
3302
+ .map(signature => ({ signature, type: this.returnOf(signature, built) }))
3303
+ .filter(({ type }) => !this.returnSaysNothing(type));
3304
+ if (returns.length === 0)
3305
+ return undefined;
3306
+ const symbolOf = (type) => type.getSymbol() ?? type.aliasSymbol;
3307
+ const symbol = symbolOf(returns[0].type);
3308
+ if (symbol === undefined)
3309
+ return undefined;
3310
+ // The receiver itself, a sibling of it, or a wrapper built from the same
3311
+ // base (a fluent builder's `.default(x)`) is the same kind of thing, not
3312
+ // an object routes are registered on: abstain.
3313
+ const mine = this.family(receiver, home);
3314
+ if ([...this.family(returns[0].type, home)].some(each => mine.has(each)))
3315
+ return undefined;
3316
+ for (const { type } of returns) {
3317
+ if (!this.isObjectType(type) || symbolOf(type) !== symbol || this.isThenable(type))
3318
+ return undefined;
3319
+ }
3320
+ if (!(symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), home)))
3321
+ return undefined;
3322
+ const chosen = returns.find(({ signature }) => built !== undefined && this.instantiates(built, signature)) ?? returns[0];
3323
+ const callable = this.namedProperties(chosen.type).some(member => this.librarySignatures(this.checker.getTypeOfSymbol(member), 'call').length > 0);
3324
+ return callable ? chosen.type : undefined;
3325
+ }
3326
+ /** The type's own declaration and every base it extends or implements that its home declares. */
3327
+ family(type, home) {
3328
+ const found = new Set();
3329
+ const visit = (each, depth) => {
3330
+ const target = (each.objectFlags ?? 0) & ts.ObjectFlags.Reference ? each.target : each;
3331
+ const symbol = target.getSymbol();
3332
+ if (symbol === undefined || found.has(symbol) || depth > 12)
3333
+ return;
3334
+ if (!(symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), home)))
3335
+ return;
3336
+ found.add(symbol);
3337
+ if ((target.objectFlags ?? 0) & ts.ObjectFlags.ClassOrInterface) {
3338
+ for (const base of this.checker.getBaseTypes(target))
3339
+ visit(base, depth + 1);
3340
+ }
3341
+ };
3342
+ visit(type, 0);
3343
+ return found;
3344
+ }
3345
+ /** A parameter of the signature accepts a string, positionally or at a key of its object. */
3346
+ takesString(signature) {
3347
+ let read = this.takesStringRead.get(signature);
3348
+ if (read === undefined)
3349
+ this.takesStringRead.set(signature, (read = this.readTakesString(signature)));
3350
+ return read;
3351
+ }
3352
+ readTakesString(signature) {
3353
+ const count = signature.getParameters().length;
3354
+ for (let index = 0; index < count; index++) {
3355
+ const slot = this.keyParameterAt(signature, index);
3356
+ if (slot === undefined || slot === VARIADIC)
3357
+ continue;
3358
+ if (this.acceptsString(this.throughConditional(slot)))
3359
+ return true;
3360
+ if (this.isObjectType(slot) && this.handlerFailure(slot) !== undefined) {
3361
+ if (this.listKeys(slot, () => true).some(key => key.accepts_string))
3362
+ return true;
3363
+ }
3364
+ }
3365
+ return false;
3366
+ }
3367
+ /** The type has a callable `then`: awaiting it gives something else. */
3368
+ isThenable(type) {
3369
+ const then = this.checker.getPropertyOfType(type, 'then');
3370
+ return then !== undefined && this.checker.getNonNullableType(this.checker.getTypeOfSymbol(then)).getCallSignatures().length > 0;
3371
+ }
3075
3372
  /**
3076
3373
  * The value exports a specifier lists (every one, or only those `only`
3077
3374
  * names), sorted by name: what its namespace holds, and `default` for a
@@ -3198,7 +3495,7 @@ class DeclarationReader {
3198
3495
  * parameters next (`handlers`), once every name is; its call, construct and
3199
3496
  * member signatures last (`fills`), once every handler is.
3200
3497
  */
3201
- outlineReceiver(pkg, receiverName, type, home, take, takeHandler, names, handlers, fills) {
3498
+ outlineReceiver(pkg, receiverName, type, home, take, names, handlers, fills) {
3202
3499
  const receiver = { receiver: receiverName, members: [] };
3203
3500
  const holder = this.surfaceHolder(pkg, type, home);
3204
3501
  // The receiver's own call and construct signatures, as a claim with a
@@ -3226,7 +3523,7 @@ class DeclarationReader {
3226
3523
  const member = { name: property.getName(), own, signatures: [] };
3227
3524
  receiver.members.push(member);
3228
3525
  handlers.push(() => {
3229
- const listed = this.listHandlers(own ? callee.signatures : library, takeHandler);
3526
+ const listed = this.listHandlers(own ? callee.signatures : library);
3230
3527
  if (listed.length > 0)
3231
3528
  member.handlers = listed;
3232
3529
  });
@@ -3242,14 +3539,14 @@ class DeclarationReader {
3242
3539
  * A parameter that declares no member lists nothing, and so does a handler
3243
3540
  * none of whose parameters do.
3244
3541
  */
3245
- listHandlers(signatures, take) {
3542
+ listHandlers(signatures) {
3246
3543
  const listed = [];
3247
3544
  const seen = new Set();
3248
3545
  const add = (arg, key, slot) => {
3249
3546
  const place = JSON.stringify([arg, key ?? null]);
3250
3547
  if (seen.has(place))
3251
3548
  return;
3252
- const params = this.listHandlerParams(slot, take);
3549
+ const params = this.listHandlerParams(slot);
3253
3550
  if (params.length === 0)
3254
3551
  return;
3255
3552
  seen.add(place);
@@ -3281,8 +3578,8 @@ class DeclarationReader {
3281
3578
  }
3282
3579
  return listed;
3283
3580
  }
3284
- /** The parameters of a function type's first signature that declare members, each member counting against the cap. */
3285
- listHandlerParams(handler, take) {
3581
+ /** The parameters of a function type's first signature that declare members, each with its members (`handlerMembersOf`). */
3582
+ listHandlerParams(handler) {
3286
3583
  const slot = this.throughConditional(this.throughConstraint(handler));
3287
3584
  if (slot === VARIADIC)
3288
3585
  return [];
@@ -3294,32 +3591,73 @@ class DeclarationReader {
3294
3591
  const listed = [];
3295
3592
  signature.getParameters().forEach((parameter, index) => {
3296
3593
  const type = this.checker.getTypeOfSymbol(parameter);
3297
- if (this.namedProperties(type).length === 0 || !take())
3594
+ if (this.namedProperties(type).length === 0)
3298
3595
  return;
3299
- const members = [];
3300
- this.listMembers(type, [], members, take);
3596
+ const { members, dropped } = this.handlerMembersOf(type);
3597
+ this.handlersDropped += dropped;
3301
3598
  listed.push({ param: index, name: parameter.getName(), type: this.printed(type), members });
3302
3599
  });
3303
3600
  return listed;
3304
3601
  }
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);
3602
+ /**
3603
+ * A handler parameter's declared members by path, down to
3604
+ * `HANDLER_MEMBER_DEPTH`, at most `HANDLER_MEMBER_CAP` of them (carrick#2966).
3605
+ * The cap keeps every top-level member before any nested one, so a wide
3606
+ * parameter loses its deepest members, never a top-level one a later member
3607
+ * name sorts after. Kept members come back parents before children, each
3608
+ * level sorted by name. Read once per type in a package, across its
3609
+ * specifiers: a type many handlers share is listed once.
3610
+ */
3611
+ handlerMembersOf(type) {
3612
+ const known = this.handlerMembers.get(type);
3613
+ if (known)
3614
+ return known;
3615
+ surfaceReadCounts.handlerMemberBuilds += 1;
3616
+ const roots = [];
3617
+ let kept = 0;
3618
+ let dropped = 0;
3619
+ // Breadth first: a level is kept whole, in name order, before the next.
3620
+ let level = [{ type, prefix: [], into: roots }];
3621
+ while (level.length > 0) {
3622
+ const next = [];
3623
+ for (const { type: holder, prefix, into } of level) {
3624
+ for (const property of sortedByName(this.namedProperties(holder))) {
3625
+ if (kept >= HANDLER_MEMBER_CAP) {
3626
+ dropped += 1;
3627
+ continue;
3628
+ }
3629
+ kept += 1;
3630
+ const memberType = this.checker.getTypeOfSymbol(property);
3631
+ const path = [...prefix, property.getName()];
3632
+ const callable = this.librarySignatures(memberType, 'call').length > 0;
3633
+ const node = {
3634
+ member: {
3635
+ path,
3636
+ type: this.printed(memberType),
3637
+ optional: (property.flags & ts.SymbolFlags.Optional) !== 0,
3638
+ function: callable,
3639
+ },
3640
+ children: [],
3641
+ };
3642
+ into.push(node);
3643
+ if (!callable && path.length < HANDLER_MEMBER_DEPTH && this.isObjectType(memberType)) {
3644
+ next.push({ type: memberType, prefix: path, into: node.children });
3645
+ }
3646
+ }
3321
3647
  }
3648
+ level = next;
3322
3649
  }
3650
+ const members = [];
3651
+ const emit = (nodes) => {
3652
+ for (const node of nodes) {
3653
+ members.push(node.member);
3654
+ emit(node.children);
3655
+ }
3656
+ };
3657
+ emit(roots);
3658
+ const listed = { members, dropped };
3659
+ this.handlerMembers.set(type, listed);
3660
+ return listed;
3323
3661
  }
3324
3662
  listSignatures(signatures, holder, take) {
3325
3663
  const listed = [];
@@ -3356,7 +3694,11 @@ class DeclarationReader {
3356
3694
  param.literals = [...literals].sort();
3357
3695
  params.push(param);
3358
3696
  }
3359
- listed.push({ params, returns: this.printed(this.checker.getReturnTypeOfSignature(signature)) });
3697
+ const entry = { params, returns: this.printed(this.checker.getReturnTypeOfSignature(signature)) };
3698
+ const candidates = this.listResultCandidates ? resultCandidatesOf(this.checker, signature) : undefined;
3699
+ if (candidates !== undefined)
3700
+ entry.result_candidates = candidates;
3701
+ listed.push(entry);
3360
3702
  }
3361
3703
  return listed;
3362
3704
  }
@@ -3403,9 +3745,15 @@ class DeclarationReader {
3403
3745
  * so where the cut falls does not depend on where the package is installed.
3404
3746
  */
3405
3747
  printed(type) {
3748
+ const known = this.printedTexts.get(type);
3749
+ if (known !== undefined)
3750
+ return known;
3751
+ surfaceReadCounts.typePrints += 1;
3406
3752
  const node = this.checker.typeToTypeNode(type, undefined, PRINT_FLAGS);
3407
3753
  const text = node === undefined ? this.checker.typeToString(type) : printTypeNode(this.orderUnions(node));
3408
- return truncate(this.scrubbed(text));
3754
+ const listed = truncate(this.scrubbed(text));
3755
+ this.printedTexts.set(type, listed);
3756
+ return listed;
3409
3757
  }
3410
3758
  /** `text` with the service root written as `<root>`. */
3411
3759
  scrubbed(text) {
@@ -3579,8 +3927,10 @@ class DeclarationReader {
3579
3927
  /** Judge the next check as a claim about `pkg`. */
3580
3928
  setPackage(pkg) {
3581
3929
  const named = packageNameOf(pkg);
3582
- if (named !== this.currentPackage)
3930
+ if (named !== this.currentPackage) {
3583
3931
  this.declared.clear();
3932
+ this.handlerMembers.clear();
3933
+ }
3584
3934
  this.currentPackage = named;
3585
3935
  }
3586
3936
  /**
@@ -3873,6 +4223,12 @@ class DeclarationReader {
3873
4223
  */
3874
4224
  /** The longest member path a handler parameter's members are listed to (`['raw', 'url']`). */
3875
4225
  const HANDLER_MEMBER_DEPTH = 2;
4226
+ /**
4227
+ * The most members one handler parameter lists (carrick#2966), top-level
4228
+ * first. The store's server question shows at most 300 of them in the same
4229
+ * order, so this must stay at least that.
4230
+ */
4231
+ const HANDLER_MEMBER_CAP = 500;
3876
4232
  const PRINT_FLAGS = ts.NodeBuilderFlags.AllowUniqueESSymbolType |
3877
4233
  ts.NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope |
3878
4234
  ts.NodeBuilderFlags.IgnoreErrors |