@suss/adapter-typescript 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, PropertyAccessExpression, TaggedTemplateExpression, Project, ImportDeclaration, Symbol, Expression, NewExpression, Identifier } from 'ts-morph';
2
- import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, EffectArg, CallOps, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, RawDeclaredContract, ContractPattern, AstCapableOps, ResponsePropertyMapping } from '@suss/extractor';
2
+ import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, EffectArg, CallOps, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, configuredCallOption, RawDeclaredContract, ContractPattern, AstCapableOps, ResponsePropertyMapping } from '@suss/extractor';
3
3
  import { BehavioralSummary, MessageBusSemantics, DeployableUnit, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
4
+ import { z } from 'zod';
4
5
  import { Proof, Atom } from '@suss/datalog';
5
6
  import { ResolutionExplanation } from '@suss/resolution';
6
7
 
@@ -140,6 +141,14 @@ interface PackFunnel {
140
141
  * while this is non-empty.
141
142
  */
142
143
  failures: PackFailure[];
144
+ /**
145
+ * Registration helpers this pack's config asked for that no call in
146
+ * the run matched. The helper belongs to the project, so a spelling
147
+ * that matches nothing is a config mistake with no other symptom: the
148
+ * routes go missing and every count reads the same as a project
149
+ * without them.
150
+ */
151
+ helpersUnmatched: string[];
143
152
  /**
144
153
  * What the pack wrote as data rather than as code, or null for a pack
145
154
  * written as a hand-rolled walk. This is the one thing in the funnel
@@ -247,6 +256,7 @@ declare class ResolutionStore {
247
256
  private readonly importedNames;
248
257
  private readonly writtenValues;
249
258
  private readonly importOrigins;
259
+ private readonly subjectConstructions;
250
260
  /**
251
261
  * A file's table depends only on its re-export closure, which
252
262
  * `collectExports` extracts whole before answering, so later
@@ -288,6 +298,36 @@ declare class ResolutionStore {
288
298
  * GraphQL document kept in a constant in another file.
289
299
  */
290
300
  resolveWrittenValue(value: Node): Node | null;
301
+ /**
302
+ * The construction behind a registration subject: the call or `new`
303
+ * this value is written as, kept only when its callee comes from
304
+ * `importModule` under `importName`. A value written as two
305
+ * different things comes back null, since keying a route on the
306
+ * wrong app is worse than not keying it.
307
+ */
308
+ subjectConstructionOf(value: Node, importModule: string, importName: string): Node | null;
309
+ /**
310
+ * How many constructions this value was written as, which is what
311
+ * `subjectConstructionOf` weighed before it declined. Ask it after
312
+ * that call, so the walk it needs has already been paid for.
313
+ */
314
+ subjectCandidateCountOf(value: Node, importModule: string, importName: string): number;
315
+ /**
316
+ * The single-answer policy over the written-value walk, then the
317
+ * origin check on the one candidate. A candidate failing the check
318
+ * is settled refusal rather than a reason to widen: its own file's
319
+ * import facts are already in, and more files only add candidates.
320
+ */
321
+ private lookupSubjectConstruction;
322
+ /**
323
+ * Whether the construction's callee was imported from this module
324
+ * under this name. A default import records its name as "default",
325
+ * and a pack declares a default export by the local name people
326
+ * give it, so "default" counts when the callee is spelled with the
327
+ * declared name.
328
+ */
329
+ private constructionComesFrom;
330
+ private calleeNameOf;
291
331
  /**
292
332
  * Which names from `modules` this value comes down to, either by being
293
333
  * one of them under a local name or by calling into one. Getting
@@ -355,6 +395,15 @@ declare class ResolutionStore {
355
395
  * construction makes an instance of. The README says why.
356
396
  */
357
397
  private lookupWritten;
398
+ /**
399
+ * Read these files' facts now, before anything asks a question.
400
+ *
401
+ * A query widens along the imports of the file it starts in, which
402
+ * is where a value's definition is. What a caller passed is the other
403
+ * direction, so a question about a parameter is answered only by
404
+ * files the walk never reaches on its own.
405
+ */
406
+ extractFiles(sourceFiles: Iterable<SourceFile>): void;
358
407
  /**
359
408
  * Which of these files reach any of these packages, following
360
409
  * project-local re-export chains. A barrel package that re-exports an
@@ -405,6 +454,44 @@ type FunctionRoot = FunctionDeclaration | FunctionExpression | ArrowFunction | M
405
454
  */
406
455
  declare function collectAncestorBranches(terminalNode: Node, functionRoot: FunctionRoot): RawCondition[];
407
456
 
457
+ /**
458
+ * unfollowedCall.ts: naming the calls the reachable closure stops at.
459
+ *
460
+ * The closure follows a call by resolving its callee to a function with a
461
+ * body. When that fails the edge is dropped, and a unit whose body is full
462
+ * of dropped edges produces the same empty summary as a unit that does
463
+ * nothing. This module says which kind of stop a call site is, and which
464
+ * kinds are worth leaving a gap for.
465
+ *
466
+ * The README beside this file explains where that line is drawn, and why
467
+ * a call into a dependency stays on the other side of it.
468
+ */
469
+
470
+ /**
471
+ * Why the walk stopped.
472
+ *
473
+ * `noBody` states a shape and nothing else: a method on an interface, an
474
+ * abstract method, an ambient declaration. `unsettledValue` is declared
475
+ * as something other than a function, with something in it that could
476
+ * not be read. `multipleSources` reaches two different functions, so no
477
+ * single body can be followed. `outsideRun` is a declaration in a
478
+ * dependency, whose source this run never read. `noDeclaration` is a
479
+ * callee nothing declares, which is what a call on an untyped value
480
+ * comes to. `callerSupplied` is a parameter of the function being
481
+ * scanned, so the call runs whatever its caller handed in.
482
+ * `multipleReceivers` is a registration whose receiver comes down to
483
+ * more than one thing, so nothing says which one it registers on.
484
+ */
485
+ type UnfollowedReason = "noBody" | "unsettledValue" | "multipleSources" | "outsideRun" | "noDeclaration" | "callerSupplied" | "multipleReceivers";
486
+ /** One call the walk met and could not follow. */
487
+ interface UnfollowedCall {
488
+ /** The callee as the source writes it, `this.dao.getEditions` say. */
489
+ readonly callee: string;
490
+ readonly reason: UnfollowedReason;
491
+ /** How many things the walk reached where it needed one. */
492
+ readonly candidates?: number;
493
+ }
494
+
408
495
  interface ClientCallSite {
409
496
  callExpression: CallExpression;
410
497
  /** Method name on the client object (e.g. "getUser"), null for bare calls like fetch() */
@@ -556,6 +643,19 @@ interface DiscoveredUnit {
556
643
  /** Null when the pack knows the wire but not the channel on it. */
557
644
  channel: string | null;
558
645
  };
646
+ /**
647
+ * Populated by a pack's `discoverUnits` callback for a deployed unit
648
+ * nothing else routes to, which is reached by being invoked by name.
649
+ * The adapter builds a `unit-invocation` binding from it, which pairs
650
+ * with the calls that name the same unit.
651
+ */
652
+ invocationInfo?: DeployableUnit;
653
+ /**
654
+ * Identity of the routable this unit was registered on, when a
655
+ * registration call put it there. What the wrapper index is keyed on,
656
+ * since a wrapper is registered on the routable and not on the unit.
657
+ */
658
+ registrationSubjectId?: string;
559
659
  /** The thing that gets deployed and runs this unit, when known. */
560
660
  deployableUnit?: DeployableUnit;
561
661
  /**
@@ -573,6 +673,12 @@ interface DiscoveredUnit {
573
673
  * summary as an `unreadOutcome` gap.
574
674
  */
575
675
  unreadBinding?: string;
676
+ /**
677
+ * A call the handler met and could not follow, which reaches the
678
+ * summary as an `unfollowedCall` gap beside the ones the closure
679
+ * walk records.
680
+ */
681
+ unfollowed?: UnfollowedCall;
576
682
  }
577
683
 
578
684
  /**
@@ -597,12 +703,19 @@ interface MountPrefixIndex {
597
703
  */
598
704
  prefixForId?(childId: string): string;
599
705
  }
706
+ /**
707
+ * What the project helper index says it has already expanded, which the
708
+ * walk consults so one registration does not become two summaries.
709
+ */
710
+ interface ExpandedRegistrations {
711
+ expandedAtCallSites(helper: Node, method: string): boolean;
712
+ }
600
713
 
601
714
  /**
602
715
  * Discover code units in `sourceFile` by running all patterns.
603
716
  * Deduplicates entries with the same function node and kind.
604
717
  */
605
- declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore, mountPrefixes?: MountPrefixIndex): DiscoveredUnit[];
718
+ declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore, mountPrefixes?: MountPrefixIndex, expandedElsewhere?: ExpandedRegistrations): DiscoveredUnit[];
606
719
 
607
720
  interface TimingReport {
608
721
  totalMs: number;
@@ -887,28 +1000,12 @@ declare function findNearestTsconfig(startDir: string): string | null;
887
1000
  */
888
1001
 
889
1002
  /**
890
- * A call the project has declared to be its own dispatcher.
891
- *
892
- * `receiver` is the type name rather than the variable: a service keeps
893
- * its dispatcher in a field, a closure or a constructor parameter, and
894
- * the type is the only thing stable across all three.
1003
+ * A call the project has declared to be its own dispatcher. The
1004
+ * declaration a project config is parsed against lives with the other
1005
+ * shared pack options, so this reader and the packs that take it under
1006
+ * `producers` describe one shape.
895
1007
  */
896
- interface ConfiguredCallSpec {
897
- /** Module that declares the receiver's type. */
898
- module: string;
899
- /** Type name of the receiver, as exported from that module. */
900
- receiver: string;
901
- /** Method that performs the send. */
902
- method: string;
903
- /** Argument index carrying the subject. */
904
- subjectArg: number;
905
- /**
906
- * Which argument the message body is. Left out when the method has no
907
- * single body argument, as a batch method taking a list of entries
908
- * does, and then no body is reported.
909
- */
910
- bodyArg?: number;
911
- }
1008
+ type ConfiguredCallSpec = z.infer<typeof configuredCallOption>;
912
1009
  interface ConfiguredCallRead {
913
1010
  /** The subject the call sends to, always a literal string. */
914
1011
  subject: string;
@@ -962,6 +1059,45 @@ declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern, fr
962
1059
  */
963
1060
  declare function readContractForClientCall(callExpression: Node, methodName: string, pattern: ContractPattern, framework: string, resolution?: ResolutionStore): ContractReadResult | null;
964
1061
 
1062
+ /**
1063
+ * helperIndex.ts: reading the project's own helpers once, before any
1064
+ * file is walked.
1065
+ *
1066
+ * A pack that declares `projectHelpers` gets every helper the project
1067
+ * wrote in front of its library, each read in terms of its own
1068
+ * parameters, and gives back the patterns and recognizers it would
1069
+ * otherwise have taken as config. Those join the pack for the rest of
1070
+ * the run, so the match happens at the call site, where the arguments
1071
+ * are literal.
1072
+ *
1073
+ * Built alongside the mount and wrapper indexes, over the same files.
1074
+ * The discovery README has the ordering and the cost.
1075
+ */
1076
+
1077
+ interface ProjectHelperIndex {
1078
+ /** Discovery patterns `packName` contributed after reading the project. */
1079
+ patternsFor(packName: string): readonly DiscoveryPattern[];
1080
+ /**
1081
+ * Recognizers the packs contributed. They match calls to functions
1082
+ * this project declares, so they run wherever a call can appear
1083
+ * rather than only where the pack's own import gate reaches.
1084
+ */
1085
+ contributedRecognizers(): readonly InvocationRecognizer[];
1086
+ /**
1087
+ * The helper files that shaped what `file` extracts to, so the
1088
+ * per-file cache invalidates `file` when one of them is edited.
1089
+ */
1090
+ helperFilesFor(file: SourceFile): readonly string[];
1091
+ /**
1092
+ * Whether a registration written inside `helper` with `method` is one
1093
+ * the call sites already expand. Reading it here as well would give
1094
+ * one registration two summaries, and which of the two a run kept
1095
+ * would come down to the order the files were walked in.
1096
+ */
1097
+ expandedAtCallSites(helper: Node, method: string): boolean;
1098
+ }
1099
+ declare function buildProjectHelperIndex(sourceFiles: ReadonlyArray<SourceFile>, packs: ReadonlyArray<PatternPack>, packsByFile: ReadonlyMap<SourceFile, readonly PatternPack[]>, resolution: ResolutionStore): ProjectHelperIndex;
1100
+
965
1101
  /**
966
1102
  * The one import scan.
967
1103
  *
@@ -1027,32 +1163,6 @@ interface TsDiscoveryContext {
1027
1163
  func: FunctionRoot;
1028
1164
  isDefault: boolean;
1029
1165
  }>;
1030
- /**
1031
- * For an export the project builds by calling a factory, the string
1032
- * under a property of that call's config object:
1033
- * `export const handler = makeWidgetHandler({ subject: "a.b" }, ...)`
1034
- * with `{ property: "subject" }` gives back `"a.b"`.
1035
- *
1036
- * The caller does not have to say which function was called or which
1037
- * argument the config was in. Every object argument is read. Giving the
1038
- * callee or fixing the argument position narrows that down, for a
1039
- * project whose factories would otherwise collide.
1040
- *
1041
- * Two arguments with the property set to different values give back
1042
- * null, since nothing says which one was meant.
1043
- *
1044
- * The config argument is usually an object literal at the call site;
1045
- * a variable or import is followed to the literal it resolves to.
1046
- * `as const` and parentheses around the property value are peeled.
1047
- * Anything but a string literal underneath (a computed subject, a
1048
- * template, a call) gives back null, and the caller attaches nothing
1049
- * rather than guessing.
1050
- */
1051
- exportedCallConfigString(sourceFile: SourceFile, exportName: string, spec: {
1052
- callees?: string[];
1053
- argIndex?: number;
1054
- property: string;
1055
- }): string | null;
1056
1166
  /**
1057
1167
  * Walk a function's body for return statements whose value is a
1058
1168
  * JSX element / fragment / self-closing tag. Returns true on the
@@ -1339,7 +1449,7 @@ declare function collectClientFieldAccesses(callExpr: CallExpression, func: Node
1339
1449
  /**
1340
1450
  * Evidence for drafting a dependency stub: every call the project
1341
1451
  * makes into a named package, grouped by the export it reaches, with
1342
- * the argument shapes observed at each call site. `suss stub draft`
1452
+ * the argument shapes observed at each call site. `suss infer stub`
1343
1453
  * turns this into a stub skeleton for an author to fill in. The
1344
1454
  * package's own code is never read, only the project's calls into it,
1345
1455
  * so this works for packages whose source no adapter can parse.
@@ -1501,4 +1611,4 @@ declare function computeAdapterPacksDigest(packVersions: ReadonlyArray<{
1501
1611
  version?: string;
1502
1612
  }>): string;
1503
1613
 
1504
- export { ADAPTER_VERSION, type Accessed, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExplainCallableOptions, type ExplainStats, type ExplainedResolution, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type ImportScanOptions, type ObservedStubCall, type PackFunnel, type PackGradient, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, type Timer, type TimingReport, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, type ValueLocation, type WhyExplained, WhySession, type WhySessionOptions, accessContextFor, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, functionTargetOf, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isImportedFrom, methodDeclaredIn, namedImportsOf, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, stubEvidence, stubEvidenceIn, workspaceRootFor };
1614
+ export { ADAPTER_VERSION, type Accessed, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExplainCallableOptions, type ExplainStats, type ExplainedResolution, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type ImportScanOptions, type ObservedStubCall, type PackFunnel, type PackGradient, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, type Timer, type TimingReport, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, type ValueLocation, type WhyExplained, WhySession, type WhySessionOptions, accessContextFor, buildProjectHelperIndex, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, functionTargetOf, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isImportedFrom, methodDeclaredIn, namedImportsOf, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, stubEvidence, stubEvidenceIn, workspaceRootFor };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, PropertyAccessExpression, TaggedTemplateExpression, Project, ImportDeclaration, Symbol, Expression, NewExpression, Identifier } from 'ts-morph';
2
- import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, EffectArg, CallOps, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, RawDeclaredContract, ContractPattern, AstCapableOps, ResponsePropertyMapping } from '@suss/extractor';
2
+ import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, EffectArg, CallOps, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, configuredCallOption, RawDeclaredContract, ContractPattern, AstCapableOps, ResponsePropertyMapping } from '@suss/extractor';
3
3
  import { BehavioralSummary, MessageBusSemantics, DeployableUnit, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
4
+ import { z } from 'zod';
4
5
  import { Proof, Atom } from '@suss/datalog';
5
6
  import { ResolutionExplanation } from '@suss/resolution';
6
7
 
@@ -140,6 +141,14 @@ interface PackFunnel {
140
141
  * while this is non-empty.
141
142
  */
142
143
  failures: PackFailure[];
144
+ /**
145
+ * Registration helpers this pack's config asked for that no call in
146
+ * the run matched. The helper belongs to the project, so a spelling
147
+ * that matches nothing is a config mistake with no other symptom: the
148
+ * routes go missing and every count reads the same as a project
149
+ * without them.
150
+ */
151
+ helpersUnmatched: string[];
143
152
  /**
144
153
  * What the pack wrote as data rather than as code, or null for a pack
145
154
  * written as a hand-rolled walk. This is the one thing in the funnel
@@ -247,6 +256,7 @@ declare class ResolutionStore {
247
256
  private readonly importedNames;
248
257
  private readonly writtenValues;
249
258
  private readonly importOrigins;
259
+ private readonly subjectConstructions;
250
260
  /**
251
261
  * A file's table depends only on its re-export closure, which
252
262
  * `collectExports` extracts whole before answering, so later
@@ -288,6 +298,36 @@ declare class ResolutionStore {
288
298
  * GraphQL document kept in a constant in another file.
289
299
  */
290
300
  resolveWrittenValue(value: Node): Node | null;
301
+ /**
302
+ * The construction behind a registration subject: the call or `new`
303
+ * this value is written as, kept only when its callee comes from
304
+ * `importModule` under `importName`. A value written as two
305
+ * different things comes back null, since keying a route on the
306
+ * wrong app is worse than not keying it.
307
+ */
308
+ subjectConstructionOf(value: Node, importModule: string, importName: string): Node | null;
309
+ /**
310
+ * How many constructions this value was written as, which is what
311
+ * `subjectConstructionOf` weighed before it declined. Ask it after
312
+ * that call, so the walk it needs has already been paid for.
313
+ */
314
+ subjectCandidateCountOf(value: Node, importModule: string, importName: string): number;
315
+ /**
316
+ * The single-answer policy over the written-value walk, then the
317
+ * origin check on the one candidate. A candidate failing the check
318
+ * is settled refusal rather than a reason to widen: its own file's
319
+ * import facts are already in, and more files only add candidates.
320
+ */
321
+ private lookupSubjectConstruction;
322
+ /**
323
+ * Whether the construction's callee was imported from this module
324
+ * under this name. A default import records its name as "default",
325
+ * and a pack declares a default export by the local name people
326
+ * give it, so "default" counts when the callee is spelled with the
327
+ * declared name.
328
+ */
329
+ private constructionComesFrom;
330
+ private calleeNameOf;
291
331
  /**
292
332
  * Which names from `modules` this value comes down to, either by being
293
333
  * one of them under a local name or by calling into one. Getting
@@ -355,6 +395,15 @@ declare class ResolutionStore {
355
395
  * construction makes an instance of. The README says why.
356
396
  */
357
397
  private lookupWritten;
398
+ /**
399
+ * Read these files' facts now, before anything asks a question.
400
+ *
401
+ * A query widens along the imports of the file it starts in, which
402
+ * is where a value's definition is. What a caller passed is the other
403
+ * direction, so a question about a parameter is answered only by
404
+ * files the walk never reaches on its own.
405
+ */
406
+ extractFiles(sourceFiles: Iterable<SourceFile>): void;
358
407
  /**
359
408
  * Which of these files reach any of these packages, following
360
409
  * project-local re-export chains. A barrel package that re-exports an
@@ -405,6 +454,44 @@ type FunctionRoot = FunctionDeclaration | FunctionExpression | ArrowFunction | M
405
454
  */
406
455
  declare function collectAncestorBranches(terminalNode: Node, functionRoot: FunctionRoot): RawCondition[];
407
456
 
457
+ /**
458
+ * unfollowedCall.ts: naming the calls the reachable closure stops at.
459
+ *
460
+ * The closure follows a call by resolving its callee to a function with a
461
+ * body. When that fails the edge is dropped, and a unit whose body is full
462
+ * of dropped edges produces the same empty summary as a unit that does
463
+ * nothing. This module says which kind of stop a call site is, and which
464
+ * kinds are worth leaving a gap for.
465
+ *
466
+ * The README beside this file explains where that line is drawn, and why
467
+ * a call into a dependency stays on the other side of it.
468
+ */
469
+
470
+ /**
471
+ * Why the walk stopped.
472
+ *
473
+ * `noBody` states a shape and nothing else: a method on an interface, an
474
+ * abstract method, an ambient declaration. `unsettledValue` is declared
475
+ * as something other than a function, with something in it that could
476
+ * not be read. `multipleSources` reaches two different functions, so no
477
+ * single body can be followed. `outsideRun` is a declaration in a
478
+ * dependency, whose source this run never read. `noDeclaration` is a
479
+ * callee nothing declares, which is what a call on an untyped value
480
+ * comes to. `callerSupplied` is a parameter of the function being
481
+ * scanned, so the call runs whatever its caller handed in.
482
+ * `multipleReceivers` is a registration whose receiver comes down to
483
+ * more than one thing, so nothing says which one it registers on.
484
+ */
485
+ type UnfollowedReason = "noBody" | "unsettledValue" | "multipleSources" | "outsideRun" | "noDeclaration" | "callerSupplied" | "multipleReceivers";
486
+ /** One call the walk met and could not follow. */
487
+ interface UnfollowedCall {
488
+ /** The callee as the source writes it, `this.dao.getEditions` say. */
489
+ readonly callee: string;
490
+ readonly reason: UnfollowedReason;
491
+ /** How many things the walk reached where it needed one. */
492
+ readonly candidates?: number;
493
+ }
494
+
408
495
  interface ClientCallSite {
409
496
  callExpression: CallExpression;
410
497
  /** Method name on the client object (e.g. "getUser"), null for bare calls like fetch() */
@@ -556,6 +643,19 @@ interface DiscoveredUnit {
556
643
  /** Null when the pack knows the wire but not the channel on it. */
557
644
  channel: string | null;
558
645
  };
646
+ /**
647
+ * Populated by a pack's `discoverUnits` callback for a deployed unit
648
+ * nothing else routes to, which is reached by being invoked by name.
649
+ * The adapter builds a `unit-invocation` binding from it, which pairs
650
+ * with the calls that name the same unit.
651
+ */
652
+ invocationInfo?: DeployableUnit;
653
+ /**
654
+ * Identity of the routable this unit was registered on, when a
655
+ * registration call put it there. What the wrapper index is keyed on,
656
+ * since a wrapper is registered on the routable and not on the unit.
657
+ */
658
+ registrationSubjectId?: string;
559
659
  /** The thing that gets deployed and runs this unit, when known. */
560
660
  deployableUnit?: DeployableUnit;
561
661
  /**
@@ -573,6 +673,12 @@ interface DiscoveredUnit {
573
673
  * summary as an `unreadOutcome` gap.
574
674
  */
575
675
  unreadBinding?: string;
676
+ /**
677
+ * A call the handler met and could not follow, which reaches the
678
+ * summary as an `unfollowedCall` gap beside the ones the closure
679
+ * walk records.
680
+ */
681
+ unfollowed?: UnfollowedCall;
576
682
  }
577
683
 
578
684
  /**
@@ -597,12 +703,19 @@ interface MountPrefixIndex {
597
703
  */
598
704
  prefixForId?(childId: string): string;
599
705
  }
706
+ /**
707
+ * What the project helper index says it has already expanded, which the
708
+ * walk consults so one registration does not become two summaries.
709
+ */
710
+ interface ExpandedRegistrations {
711
+ expandedAtCallSites(helper: Node, method: string): boolean;
712
+ }
600
713
 
601
714
  /**
602
715
  * Discover code units in `sourceFile` by running all patterns.
603
716
  * Deduplicates entries with the same function node and kind.
604
717
  */
605
- declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore, mountPrefixes?: MountPrefixIndex): DiscoveredUnit[];
718
+ declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore, mountPrefixes?: MountPrefixIndex, expandedElsewhere?: ExpandedRegistrations): DiscoveredUnit[];
606
719
 
607
720
  interface TimingReport {
608
721
  totalMs: number;
@@ -887,28 +1000,12 @@ declare function findNearestTsconfig(startDir: string): string | null;
887
1000
  */
888
1001
 
889
1002
  /**
890
- * A call the project has declared to be its own dispatcher.
891
- *
892
- * `receiver` is the type name rather than the variable: a service keeps
893
- * its dispatcher in a field, a closure or a constructor parameter, and
894
- * the type is the only thing stable across all three.
1003
+ * A call the project has declared to be its own dispatcher. The
1004
+ * declaration a project config is parsed against lives with the other
1005
+ * shared pack options, so this reader and the packs that take it under
1006
+ * `producers` describe one shape.
895
1007
  */
896
- interface ConfiguredCallSpec {
897
- /** Module that declares the receiver's type. */
898
- module: string;
899
- /** Type name of the receiver, as exported from that module. */
900
- receiver: string;
901
- /** Method that performs the send. */
902
- method: string;
903
- /** Argument index carrying the subject. */
904
- subjectArg: number;
905
- /**
906
- * Which argument the message body is. Left out when the method has no
907
- * single body argument, as a batch method taking a list of entries
908
- * does, and then no body is reported.
909
- */
910
- bodyArg?: number;
911
- }
1008
+ type ConfiguredCallSpec = z.infer<typeof configuredCallOption>;
912
1009
  interface ConfiguredCallRead {
913
1010
  /** The subject the call sends to, always a literal string. */
914
1011
  subject: string;
@@ -962,6 +1059,45 @@ declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern, fr
962
1059
  */
963
1060
  declare function readContractForClientCall(callExpression: Node, methodName: string, pattern: ContractPattern, framework: string, resolution?: ResolutionStore): ContractReadResult | null;
964
1061
 
1062
+ /**
1063
+ * helperIndex.ts: reading the project's own helpers once, before any
1064
+ * file is walked.
1065
+ *
1066
+ * A pack that declares `projectHelpers` gets every helper the project
1067
+ * wrote in front of its library, each read in terms of its own
1068
+ * parameters, and gives back the patterns and recognizers it would
1069
+ * otherwise have taken as config. Those join the pack for the rest of
1070
+ * the run, so the match happens at the call site, where the arguments
1071
+ * are literal.
1072
+ *
1073
+ * Built alongside the mount and wrapper indexes, over the same files.
1074
+ * The discovery README has the ordering and the cost.
1075
+ */
1076
+
1077
+ interface ProjectHelperIndex {
1078
+ /** Discovery patterns `packName` contributed after reading the project. */
1079
+ patternsFor(packName: string): readonly DiscoveryPattern[];
1080
+ /**
1081
+ * Recognizers the packs contributed. They match calls to functions
1082
+ * this project declares, so they run wherever a call can appear
1083
+ * rather than only where the pack's own import gate reaches.
1084
+ */
1085
+ contributedRecognizers(): readonly InvocationRecognizer[];
1086
+ /**
1087
+ * The helper files that shaped what `file` extracts to, so the
1088
+ * per-file cache invalidates `file` when one of them is edited.
1089
+ */
1090
+ helperFilesFor(file: SourceFile): readonly string[];
1091
+ /**
1092
+ * Whether a registration written inside `helper` with `method` is one
1093
+ * the call sites already expand. Reading it here as well would give
1094
+ * one registration two summaries, and which of the two a run kept
1095
+ * would come down to the order the files were walked in.
1096
+ */
1097
+ expandedAtCallSites(helper: Node, method: string): boolean;
1098
+ }
1099
+ declare function buildProjectHelperIndex(sourceFiles: ReadonlyArray<SourceFile>, packs: ReadonlyArray<PatternPack>, packsByFile: ReadonlyMap<SourceFile, readonly PatternPack[]>, resolution: ResolutionStore): ProjectHelperIndex;
1100
+
965
1101
  /**
966
1102
  * The one import scan.
967
1103
  *
@@ -1027,32 +1163,6 @@ interface TsDiscoveryContext {
1027
1163
  func: FunctionRoot;
1028
1164
  isDefault: boolean;
1029
1165
  }>;
1030
- /**
1031
- * For an export the project builds by calling a factory, the string
1032
- * under a property of that call's config object:
1033
- * `export const handler = makeWidgetHandler({ subject: "a.b" }, ...)`
1034
- * with `{ property: "subject" }` gives back `"a.b"`.
1035
- *
1036
- * The caller does not have to say which function was called or which
1037
- * argument the config was in. Every object argument is read. Giving the
1038
- * callee or fixing the argument position narrows that down, for a
1039
- * project whose factories would otherwise collide.
1040
- *
1041
- * Two arguments with the property set to different values give back
1042
- * null, since nothing says which one was meant.
1043
- *
1044
- * The config argument is usually an object literal at the call site;
1045
- * a variable or import is followed to the literal it resolves to.
1046
- * `as const` and parentheses around the property value are peeled.
1047
- * Anything but a string literal underneath (a computed subject, a
1048
- * template, a call) gives back null, and the caller attaches nothing
1049
- * rather than guessing.
1050
- */
1051
- exportedCallConfigString(sourceFile: SourceFile, exportName: string, spec: {
1052
- callees?: string[];
1053
- argIndex?: number;
1054
- property: string;
1055
- }): string | null;
1056
1166
  /**
1057
1167
  * Walk a function's body for return statements whose value is a
1058
1168
  * JSX element / fragment / self-closing tag. Returns true on the
@@ -1339,7 +1449,7 @@ declare function collectClientFieldAccesses(callExpr: CallExpression, func: Node
1339
1449
  /**
1340
1450
  * Evidence for drafting a dependency stub: every call the project
1341
1451
  * makes into a named package, grouped by the export it reaches, with
1342
- * the argument shapes observed at each call site. `suss stub draft`
1452
+ * the argument shapes observed at each call site. `suss infer stub`
1343
1453
  * turns this into a stub skeleton for an author to fill in. The
1344
1454
  * package's own code is never read, only the project's calls into it,
1345
1455
  * so this works for packages whose source no adapter can parse.
@@ -1501,4 +1611,4 @@ declare function computeAdapterPacksDigest(packVersions: ReadonlyArray<{
1501
1611
  version?: string;
1502
1612
  }>): string;
1503
1613
 
1504
- export { ADAPTER_VERSION, type Accessed, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExplainCallableOptions, type ExplainStats, type ExplainedResolution, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type ImportScanOptions, type ObservedStubCall, type PackFunnel, type PackGradient, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, type Timer, type TimingReport, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, type ValueLocation, type WhyExplained, WhySession, type WhySessionOptions, accessContextFor, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, functionTargetOf, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isImportedFrom, methodDeclaredIn, namedImportsOf, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, stubEvidence, stubEvidenceIn, workspaceRootFor };
1614
+ export { ADAPTER_VERSION, type Accessed, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExplainCallableOptions, type ExplainStats, type ExplainedResolution, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type ImportScanOptions, type ObservedStubCall, type PackFunnel, type PackGradient, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, type Timer, type TimingReport, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, type ValueLocation, type WhyExplained, WhySession, type WhySessionOptions, accessContextFor, buildProjectHelperIndex, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, functionTargetOf, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isImportedFrom, methodDeclaredIn, namedImportsOf, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, stubEvidence, stubEvidenceIn, workspaceRootFor };